5

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

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

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

real    0m12.141s
user    0m1.662s
sys     0m10.073s

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

然后我使用gnomon- 一个非常方便的npm模块,用于逐项列出 CLI 工具的时间 - 将问题归结为有问题的 Python 模块。我使用了这个命令:

PIPER-ALPHA:~$ PYTHONVERBOSE=1 bpython -c 'pass' 2>&1 | tee -a /tmp/bpython-startup-messages | gnomon

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

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

......我已经强调了执行需要将近12 秒的输出行——迄今为止最长的,因为其他每一行通常需要几纳秒,或者最多几微秒,也许。

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

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

4

1 回答 1

3

我已经想通了——答案是同时具有启发性和令人尴尬的——我的解决方案可能会在遇到类似情况时帮助其他人。

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

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

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

当我偶然发现它时,我能够查明导致问题表现的代码行 - 在运行我的测试时,无休止的长时间停止同样令人讨厌,我最终控制了一些测试在中途停止。那些中止的测试运行因KeyboardInterrupt异常而终止,伴随的堆栈跟踪输出显示了拖拽的函数:

无罪的堆栈跟踪

pkg_resources模块在导入时会遍历每个名​​为 in 的扩展目录sys.path,枚举每个扩展中的每个包,然后读入并解析所有相关元数据。使用任何部分pkg_resources(它本身是基本setuptools模块的一部分)都会触发这个耗时的操作(然后至少在该特定解释器调用的生命周期内缓存该操作)。根据您的 Python 安装方式以及调用解释器的方式,您最终可能会或可能不会做某事来触发pkg_resources.会被某事触发。

负责实际枚举包的实际循环的实际函数是_initialize_master_working_set()——它是我在上面的屏幕截图中突出显示的那个。这就是我所有的KeyboardInterrupt堆栈跟踪所揭示的。从那里,很明显,令人沮丧的停止是奶酪店包装数量的急剧线性函数(这是我升级笔记本电脑后鲁莽的事情)。

virtualenv我立即着手 pip-uninstall 大约 50% 的我无偿安装的扩展,然后通过将我积极开发的大部分 Python 内容提升到独立的项目目录中,又削减了 40% 左右。

事后我觉得自己很笨,因为我巧妙地用花哨的分析工具误导了自己,然后偶然找到了真正的解决方案——一个是我自己粗心大意造成的问题,同样如此。无论如何,它仍然可能会咬其他 Pythonic 开发人员,因此值得写。特此邀请您学习我在问题分类和诊断方面的迂回冒险,真的!

于 2017-12-20T15:57:10.970 回答