我在我的Mac上使用了Homebrew安装的Python(运行OS X 10.13.1),最近,我注意到解释器需要很长时间才能启动。
在试图解决这个问题时,我用time
做了一个简单的检查:
PIPER-ALPHA:~$ time bpython -c 'pass'
real 0m12.141s
user 0m1.662s
sys 0m10.073s
...揭示了问题的严重性:12秒!
然后我使用了gnomon
--一个extremely handy npm
module用于逐项列出CLI工具的时间 - 将问题告诉了令人讨厌的Python模块。我使用了这个命令:
PIPER-ALPHA:~$ PYTHONVERBOSE=1 bpython -c 'pass' 2>&1 | tee -a /tmp/bpython-startup-messages | gnomon
... gnomon
输出显示详细的Python解释器输出发出的每一行所用的时间。它看起来像这样:
...我已经突出显示了执行时间近十二秒的输出 - 到目前为止最长,因为每一条其他线通常需要几纳秒,或者最多几微秒。
通常情况下,如果我遇到一个不稳定的Python扩展,我会自己重新编译它,或者从源代码调整它,以便在必要时正确地呈现它。但是在这种情况下,我正在处理一个c-extension模块,它是一个更大的Python标准库模块的一部分,所有这些模块都附带了Homebrew二进制包(在Homebrew中称为“瓶子”),包含这个版本的Python。
这是其他任何人都可以证明的问题吗?特别是,在类似情况下运行Python时,是否是其他人看到的问题?而且,最重要的是,我该如何解决它?我是否需要重建整个Python安装,使用Homebrew还是不使用Homebrew?
我已经想到了这一点 - 答案结果是同时发光和令人尴尬 - 我的解决方案可能会在遇到类似情况时帮助其他人。
简而言之:在加载Python解释器时,由于安装了过多的Python扩展模块,导致我经历了长时间〜12秒的停顿。这不是Python 2.7捆绑的xml.parsers.expat
模块的问题,也不是它的C-API pyexpat
扩展。
也就是说:我使用gnomon
工具,它提供了指向这些模块的直接和直接的证据,最终误导了我在哪里找到有问题的代码。
在发布我的问题后,我做了一些额外的法医调查。通过改变我在调用命令行速度检查时传递给解释器的Python代码,我发现gnomon
报告将显示相同的12秒以上的停止,但是在不同的import
语句的发生率。此外,我发现一些命令变体(例如使用pythonpy
CLT执行的变体)完全不受停止行为的困扰。
当我偶然偶然发现问题时,我能够找到导致问题表现的代码行 - 在运行我的测试时,无休止的长时间停止也不会令人讨厌,我最终控制了C-a的一些测试在中途停止。那些中止的测试运行以KeyboardInterrupt
异常终止,并且随附的stacktrace输出显示了拖动事物的功能:
... pkg_resources
模块在导入时,遍历sys.path
中命名的每个扩展目录,枚举每个扩展中的每个包,然后读入然后解析所有这些的所有相关元数据。使用pkg_resources
的任何部分(它本身是必要的setuptools
模块的一部分)会触发这个耗时的操作(至少在特定解释器调用的生命周期内缓存)。根据你的Python安装设置方式以及你如何调用你的解释器,你可能会或者可能不会最终做一些事情来触发pkg_resources
的使用,但它在Python扩展包中的用途非常广泛,所以很有可能它会被某种东西触发。
actual function responsible for the actual loop that actually enumerates the packages是_initialize_master_working_set()
- 这是我在上面的截图中突出显示的那个。这就是我所有的KeyboardInterrupt
堆栈跟踪所揭示的内容。从那里,很明显,令人沮丧的停顿是干酪店包装数量的急剧线性函数(升级我的笔记本电脑后我一直在鲁莽)。
我立即着手卸载大约50%我无偿安装的扩展,然后通过将我的大部分积极开发的Python内容提升到隔离的virtualenv
项目目录中,再减少40%左右。
之后我感到非常愚蠢,因为我设法巧妙地使用花哨的分析工具误导自己,然后偶然发现了实际的解决方案 - 一个是由于我自己粗心疏忽造成的问题,同样也是如此。无论如何,它仍然可以咬住其他Pythonic开发人员,因此值得写。特此邀请您学习我在问题分类和诊断中的迂回冒险!