Python解释器启动需要~12秒,所有这些都花费在`import pyexpat`中

fis*_*000 5 python performance homebrew python-c-api expat-parser

我在我的 Mac(运行 OS X 10.13.1)上使用Homebrew安装的 Python,最近,我注意到解释器需要很长时间才能启动。

在着手尝试解决这个问题时,我做了一个简单的检查time

PIPER-ALPHA:~$ time bpython -c 'pass'

real    0m12.141s
user    0m1.662s
sys     0m10.073s
Run Code Online (Sandbox Code Playgroud)

……这揭示了问题的严重性:12 秒!

然后,我使用gnomon了一个非常方便的npm模块,用于逐项列出 CLI 工具的计时——将问题筛选到有问题的 Python 模块。我使用了这个命令:

PIPER-ALPHA:~$ PYTHONVERBOSE=1 bpython -c 'pass' 2>&1 | tee -a /tmp/bpython-startup-messages | gnomon
Run Code Online (Sandbox Code Playgroud)

...gnomon输出显示了详细 Python 解释器输出发出的每一行所花费的时间。它看起来像这样:

使用 gnomon 计时的详细 Python 解释器启动输出

……我已经突出显示了执行耗时近12 秒的输出行——迄今为止最长的,因为每隔一行通常需要几纳秒,或者最多几秒,也许。

通常,如果我遇到一个不稳定的 Python 扩展,我会自己重新编译它,或者从源代码调整它,以便在必要时正确地使其不存在问题。但在这种情况下,我正在处理一个 c-extension 模块,它是更大的 Python 标准库模块的一部分,所有这些模块都随 Homebrew 二进制包(在 Homebrew argot 中称为“瓶子”)一起提供,其中包含这个版本的 Python。

这是其他人可以证明的问题吗?特别是,其他人在类似情况下运行 Python 时是否会遇到这个问题?而且,最重要的是,我该如何修复它?我是否需要使用 Homebrew 或不使用 Homebrew 重建整个 Python 安装?

fis*_*000 4

我已经弄清楚了 \xe2\x80\x93 答案结果是同时具有启发性和令人尴尬的 \xe2\x80\x93 ,我的解决方案可能会在其他人面临类似情况时帮助他们。

\n\n

简而言之:在加载 Python 解释器时,我经历了长达 12 秒的令人恼火的停顿,这是由于安装了过多的 Python 扩展模块造成的。这不是Python 2.7\xe2\x80\x99s 捆绑模块的问题xml.parsers.expat,也不是其 C-APIpyexpat扩展的问题。

\n\n

也就是说:我使用该gnomon工具提供了指向这些模块的看似直接和直接的证据,最终误导了我关于在哪里找到有问题的代码的结论。

\n\n

在发布我的问题后,我做了一些额外的取证调查。通过改变我在调用命令行速度检查时传递给解释器的 Python 代码,我发现报告gnomon将显示相同的十二秒多的暂停,但出现不同的import语句。此外,我发现某些命令变体(例如使用pythonpyCLT 执行的命令变体)根本不受停止行为的困扰。

\n\n

当我在运行测试时偶然发现 \xe2\x80\x93 时,我能够查明导致问题 \xe2\x80\x99 表现的代码行,无休止的长时间暂停同样令人烦恼,我结束了up control-C\xe2\x80\x99ing 中途停止一些测试。这些中止的测试运行因KeyboardInterrupt异常而终止,随附的堆栈跟踪输出显示了正在拖动的函数:

\n\n

开脱的堆栈跟踪

\n\n

\xe2\x80\xa6 该pkg_resources模块在导入时会遍历 中指定的每个扩展目录sys.path,枚举每个扩展中的每个包,然后读入并解析所有这些扩展的所有关联元数据。使用 的任何部分pkg_resources(其本身是基本setuptools模块的一部分)都会触发此耗时的操作(然后至少在该特定解释器调用\xe2\x80\x99s 生命周期内被缓存)。根据 Python 安装的设置方式以及调用解释器的方式,您最终可能会或可能不会执行某些操作来触发 的使用pkg_resources,但它在 Python 扩展包中得到了相当广泛的使用,因此很有可能\xe2\x80\x99 会被某些东西触发。

\n\n

负责实际枚举包的实际循环的实际函数是\ _initialize_master_working_set()xe2\x80\x93 it\xe2\x80\x99s 是我在上面的屏幕截图中突出显示的那个 \xe2\x80\x99。这就是我所有的KeyboardInterrupt堆栈跟踪所揭示的内容。从那里,很明显,令人沮丧的停止是存在的奶酪店包裹数量的陡峭线性函数(这是我在升级笔记本电脑后鲁莽的事情)。

\n\n

我立即开始 pip 卸载我无偿安装的大约 50% 的扩展,然后通过将我积极开发的大部分 Python 东西提升到独立的项目目录中来减少另外 40% 左右的扩展virtualenv

\n\n

后来我觉得自己很愚蠢,因为我用花哨的分析工具巧妙地误导了自己,然后意外地找到了实际的解决方案 \xe2\x80\x93 ,这是我自己粗心大意造成的问题,同样如此。无论如何,它仍然可能会影响其他 Pythonic 开发人员,因此值得写一写。特此邀请您从我在问题分类和诊断方面的迂回冒险中学习!

\n