Python 解释器需要大约 12 秒才能启动,所有这些都花在了 `import pyexpat` 中
Python interpreter takes ~12 seconds to start up, all of which is spent in `import pyexpat`
我在 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
- 一个 extremely handy npm
module 用于逐条列出 CLI 工具的时间 - 将问题归结为有问题的 Python 模块。我使用了这个命令:
PIPER-ALPHA:~$ PYTHONVERBOSE=1 bpython -c 'pass' 2>&1 | tee -a /tmp/bpython-startup-messages | gnomon
… gnomon
输出显示详细 Python 解释器输出发出的每一行所花费的时间。看起来像这样:
…我突出显示了花费将近 12 秒 执行的输出行——迄今为止最长的,因为其他每一行通常需要几纳秒,或者最多几微秒,也许。
通常,如果我遇到一个不稳定的 Python 扩展,我会自己重新编译它,或者从源代码中调整它,以在必要时正确地使其不成问题。但在这种情况下,我正在处理一个 c 扩展模块,它是一个更大的 Python 标准库模块的一部分,所有这些模块都附带 Homebrew 二进制包(在 Homebrew 中称为“bottle” argot) 包含此版本的 Python。
这是一个任何其他人都可以证明的问题吗?特别是,在类似情况下 运行 Python 时,其他人会看到这个问题吗?而且,最重要的是,我该如何解决?我是否需要重建整个 Python 安装,使用或不使用 Homebrew?
我已经弄清楚了 - 答案既有启发性又令人尴尬 - 我的解决方案可能会在遇到类似情况时帮助其他人。
简而言之:我在加载 Python 解释器时遇到的令人恼火的 ~12s 停顿是由于安装了过多的 Python 扩展模块造成的。 不是 Python 2.7 捆绑的 xml.parsers.expat
模块的问题,也不是其 C-API pyexpat
扩展的问题。
也就是说:我使用 gnomon
工具,它提供了指向这些模块的直接和直接的证据,但最终误导了我关于有问题的代码在哪里的结论找到了。
发布我的问题后,我做了一些额外的取证调查。通过改变我在调用命令行速度检查时传递给解释器的 Python 代码,我发现 gnomon
报告将显示相同的 12 秒以上的暂停,但发生率不同的 import
语句。此外,我发现一些命令变体(例如使用 pythonpy
CLT 执行的变体)根本没有受到停止行为的困扰。
当我偶然发现它时,我能够查明导致问题表现的代码行 – 而 运行 我的测试,无休止的长时间停顿同样令人讨厌,我最终control-C'ing 在中途停止一些测试。那些中止的测试运行以 KeyboardInterrupt
异常终止,并且伴随的堆栈跟踪输出揭示了拖拽东西的函数:
… 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
堆栈跟踪显示的内容。从那里,很明显,令人沮丧的停止是存在的奶酪店包裹数量的陡峭线性函数(这是我升级笔记本电脑后不计后果的事情)。
我立即着手 pip-uninstall 我无偿安装的大约 50% 的扩展,然后通过将我积极开发的大部分 Python 东西提升到隔离 virtualenv
项目目录。
后来我觉得自己很蠢,因为我用花哨的分析工具巧妙地误导了自己,然后偶然找到了真正的解决方案——一个是我自己粗心大意造成的问题,不少于此。不管怎样,它仍然是可以咬住其他 Pythonic 开发人员的东西,因此值得一写。特此邀请您学习我在问题分类和诊断方面的迂回冒险,确实如此!
我在 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
- 一个 extremely handy npm
module 用于逐条列出 CLI 工具的时间 - 将问题归结为有问题的 Python 模块。我使用了这个命令:
PIPER-ALPHA:~$ PYTHONVERBOSE=1 bpython -c 'pass' 2>&1 | tee -a /tmp/bpython-startup-messages | gnomon
… gnomon
输出显示详细 Python 解释器输出发出的每一行所花费的时间。看起来像这样:
…我突出显示了花费将近 12 秒 执行的输出行——迄今为止最长的,因为其他每一行通常需要几纳秒,或者最多几微秒,也许。
通常,如果我遇到一个不稳定的 Python 扩展,我会自己重新编译它,或者从源代码中调整它,以在必要时正确地使其不成问题。但在这种情况下,我正在处理一个 c 扩展模块,它是一个更大的 Python 标准库模块的一部分,所有这些模块都附带 Homebrew 二进制包(在 Homebrew 中称为“bottle” argot) 包含此版本的 Python。
这是一个任何其他人都可以证明的问题吗?特别是,在类似情况下 运行 Python 时,其他人会看到这个问题吗?而且,最重要的是,我该如何解决?我是否需要重建整个 Python 安装,使用或不使用 Homebrew?
我已经弄清楚了 - 答案既有启发性又令人尴尬 - 我的解决方案可能会在遇到类似情况时帮助其他人。
简而言之:我在加载 Python 解释器时遇到的令人恼火的 ~12s 停顿是由于安装了过多的 Python 扩展模块造成的。 不是 Python 2.7 捆绑的 xml.parsers.expat
模块的问题,也不是其 C-API pyexpat
扩展的问题。
也就是说:我使用 gnomon
工具,它提供了指向这些模块的直接和直接的证据,但最终误导了我关于有问题的代码在哪里的结论找到了。
发布我的问题后,我做了一些额外的取证调查。通过改变我在调用命令行速度检查时传递给解释器的 Python 代码,我发现 gnomon
报告将显示相同的 12 秒以上的暂停,但发生率不同的 import
语句。此外,我发现一些命令变体(例如使用 pythonpy
CLT 执行的变体)根本没有受到停止行为的困扰。
当我偶然发现它时,我能够查明导致问题表现的代码行 – 而 运行 我的测试,无休止的长时间停顿同样令人讨厌,我最终control-C'ing 在中途停止一些测试。那些中止的测试运行以 KeyboardInterrupt
异常终止,并且伴随的堆栈跟踪输出揭示了拖拽东西的函数:
… 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
堆栈跟踪显示的内容。从那里,很明显,令人沮丧的停止是存在的奶酪店包裹数量的陡峭线性函数(这是我升级笔记本电脑后不计后果的事情)。
我立即着手 pip-uninstall 我无偿安装的大约 50% 的扩展,然后通过将我积极开发的大部分 Python 东西提升到隔离 virtualenv
项目目录。
后来我觉得自己很蠢,因为我用花哨的分析工具巧妙地误导了自己,然后偶然找到了真正的解决方案——一个是我自己粗心大意造成的问题,不少于此。不管怎样,它仍然是可以咬住其他 Pythonic 开发人员的东西,因此值得一写。特此邀请您学习我在问题分类和诊断方面的迂回冒险,确实如此!