Python 的 pickle/cpickle/dill 可以加快导入速度吗?

Can Python's pickle/cpickle/dill speed up imports?

pickle/dill/cpickle 可以用来 pickle 导入的模块以提高导入速度吗?例如,Shapely 模块在我的系统上需要 5 秒才能找到并加载所有必需的依赖项,我真的很想避免这种情况。

我可以 pickle 我的导入一次,然后重复使用那个 pickle 而不是每次都进行缓慢的导入吗?

没有。首先也是最重要的是你不能 pickle 模块,你会得到一个错误:

>>> import pickle, re
>>> pickle.dump(re, open('/tmp/re.p', 'wb'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class 'module'>: attribute lookup module on builtins failed

从概念上讲,即使您可以序列化一个模块,也只会增加 Python 必须完成的工作量。

通常,当您说 import module 时,Python 必须:

  1. 找到模块的位置(通常是文件系统上的一个文件)
  2. 将源代码解析为内存中的字节码(如果可能,将解析后的字节码存储为.pyc file),或者如果.pyc存在则直接将其加载到内存中
  3. 在模块首次加载时执行任何应该运行的代码

如果您要以某种方式 pickle 一个模块,您实际上是用您自己的 half-baked 解决方案替换第 2 步。

  1. 找到 pickle 的位置(通常是文件系统上的一个文件)
  2. 将其拆回 Python 模块
  3. 在模块首次加载时执行任何应该运行的代码

我们可以安全地假设 unpickling 会比 Python 的 built-in 字节码格式慢,因为如果不是这样 Python 无论如何都会在幕后使用 pickling。

更重要的是,解析 Python 文件并不(非常)昂贵,而且几乎不会花费任何时间。任何真正的减速都会发生在第 3 步,我们没有改变这一点。您可能会问是否有某种方法可以跳过 pickling 的第三步,但在一般情况下不,这是不可能的,因为无法保证模块不会对环境的其余部分进行更改。

现在您可能知道一些关于 Shapely 模块的特别之处,特别是让您说 "all the work Shapely does when imported could safely be cached between runs"。在那种情况下,正确的做法是 contribute 将这种缓存行为添加到库中并缓存 data Shapely 正在加载,而不是 code Python 正在导入。

虽然 dill 可以序列化一个模块,但从它如何序列化一个模块可以看出它不节省 import 上的工作。当 dill 序列化一个模块时,它确实会调用一个函数,然后它会导入该模块。所以,正如@dimo414 所说,答案是否定的。

>>> import dill
>>> import re
>>> _re = dill.dumps(re)
>>> re_ = dill.loads(_re)
>>> re_
<module 're' from '/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.pyc'>
>>> _re
'\x80\x02cdill.dill\n_import_module\nq\x00U\x02req\x01\x85q\x02Rq\x03.'
>>> 

导入延迟很可能是由于加载 GEOS 库的依赖共享对象造成的。

也许可以优化这个,但会非常困难。一种方法是构建一个静态编译的自定义 python 解释器,其中内置所有 DLL 和扩展模块。但维护它是一个主要的 PITA(相信我 - 我这样做是为了工作)。

另一种选择是将您的应用程序变成一项服务,这样只会产生启动解释器一次的运行时成本。

这是否合适,要看你的实际问题。