在Python中如何用pip来加快安装速度?

1,566 阅读7分钟

安装你的Python应用程序的依赖项可能会慢得令人吃惊。无论是在CI中运行测试、构建Docker镜像,还是安装一个应用程序,下载和安装依赖都会花费一些时间。

那么,你如何用pip 来加快安装速度呢?

在这篇文章中,我将介绍。

  • 避免从源代码安装的缓慢路径。
  • pip 下载速度,以及替代品。Pipenv和Poetry。
  • 一个有用的pip 选项,有时可以大大加快安装速度。

避免从源代码安装

当你安装一个 Python 包时,通常有两种安装方式。

  • 打包后的源文件,通常是带有setup.py.tar.gz 。在这种情况下,安装时往往需要运行 Python 代码 (有点慢),有时还需要编译大量的 C/C++/Rust 代码 (可能会非常慢)。
  • 一个轮子 (.whl 文件),可以直接解压到文件系统上,不需要运行代码或编译本地扩展。

如果可能的话,你想安装轮子,因为从源代码安装会比较慢。如果你需要编译大量的C语言代码,从源码安装会更慢;你将需要自己编译,而不是依赖预编译的二进制文件。

为了确保你尽可能多地安装轮子。

  • 在安装依赖项之前,确保你使用的是最新版本的pip。二进制轮子有时需要比你当前 Python 默认打包的pip 更新的版本。
  • 不要使用 Alpine Linux;坚持使用使用glibc 的 Linux 发行版,例如 Debian/Ubuntu/RedHat/等等。标准的 Linux 轮子需要glibc ,但 Alpine 使用musl C 库。像Alpine这样基于musl 的发行版的轮子开始变得可用,但它们仍然不那么普遍。

比较pip 、Pipenv 和 Poetry 之间的安装速度

默认的 Python 软件包管理器是pip ,但你也可以使用PipenvPoetry,它们都增加了额外的功能,如 virtualenv 管理。我比较了这三者的速度。

方法论

安装Python软件包包括两个步骤。

  1. 下载软件包。
  2. 安装已经下载的包。

默认情况下,Python包管理器会将下载的包缓存在磁盘上,所以如果你在不同的virtualenv中第二次安装它们,包就不需要重新下载。因此,我测量了两种变体:一种是必须下载软件包的冷缓存,另一种是软件包在本地已经可用的暖缓存。

在所有情况下,我都确保提前创建了virtualenvs,对于pip ,我确保在requirements.txt ,以配合其他两个软件包管理器默认的哈希验证。

我在安装pandasmatplotlib 时使用了横向依赖关系,结果是总共安装了12个不同的软件包。

结果

下面是每次安装所花的时间,同时测量挂钟和CPU时间

工具缓存挂钟时间CPU时间
pip 22.1.1寒冷16.2s10.7s
pip 22.1.1暖气10.5s9.4s
管子2022.5.2冷的12.5s26.0s
管道系统2022.5.2暖气9.7s25.2s
诗歌 1.1.13寒冷12.1s18s
诗歌1.1.13温暖的10.2s17.8s

一些需要注意的事情。

  • pip 当缓存是冷的时候,按壁钟时间计算是最慢的。
  • 当缓存是热的,即软件包已经被下载时,它们之间的壁挂时间其实并没有什么不同。
  • Pipenv和Poetry都使用了并行性,我们可以从CPU时间高于壁时钟时间看出;pip 目前是单线程的。
  • 与其他两个相比,Pipenv使用了相当多的CPU;Poetry要好一点,但仍然高于pip

这个例子是在安装了12个包的情况下运行的;如果有更多的依赖关系,Poetry的并行安装可能会有更大的影响。

保持缓存的温度

请注意,在所有情况下,你都会因为有一个温暖的缓存而得到加速,也就是说,重复使用已经下载的软件包。在你的本地机器上,这会自动发生。在大多数CI服务中,你的缓存一开始就是空的。

为了解决这个问题,大多数CI系统会有一些方法在运行结束后存储一个缓存目录,然后在下一次运行开始时加载。如果你使用的是GitHub动作,你可以使用用于设置Python的动作中的内置缓存支持

但这仍然不如在专用机器上运行的速度快:存储和加载缓存也需要时间。

通过禁用版本检查来提高(非常小的)速度

在启动时,pip 可能会检查你是否在运行最新的版本,如果不是的话,会打印一个警告。你可以像这样禁用这个检查。

pip --disable-pip-version-check install ...

这为我节省了大约0.2-0.3秒,不是一个非常明显的改进;实际改进可能取决于你的网络速度和其他因素。

在禁用编译的情况下走得更快(有时)。

我们可以做得更好吗?在某些情况下,是的。

在软件包被下载(如果它们没有被缓存在本地)并安装到文件系统上之后,软件包管理器会做最后一步:它们将.py 源文件编译成.pyc 字节码文件,并将它们存储在__pycache__ 目录中。这与编译 C 语言扩展不一样,这只是一种优化,使 Python 代码的加载在启动时更快。不用在导入时编译.pyc ,而是.pyc 已经在那里了。

事实证明,字节码编译占用了pip install 的大量时间。但是你可以通过调用pip install --no-compile 来禁用这个步骤。

下面是有和没有.pyc 编译的软件包安装时间的比较,在这两种情况下,缓存是温暖的,所以不需要下载。

安装方法缓存挂钟时间CPU时间
pip install温热10.5s9.4s
pip install --no-compile暖机4.8s4.0s

那么,你应该总是使用这个选项吗?不一定。仅仅因为pip install 更快并不意味着你总体上节省了时间。

你导入的任何模块仍然需要被编译成.pyc ,只是这项工作将在 Python 运行时发生,而不是在软件包安装时。所以如果你导入了所有或大多数模块,总体上你可能根本就没有节省任何时间,你只是把工作转移到了不同的地方。

然而,在其他情况下,--no-compile 会为你节省时间。例如,在你的测试设置中,你可能为集成测试安装了许多第三方软件包,但只使用了这些库中的少量代码。因此,没有必要编译很多你不会使用的模块。

目前,Pipenv和Poetry似乎都不支持这个选项。

包的安装可以更快

考虑到有多少人使用Python,缓慢的软件包安装会增加。

很难估计世界上有多少pip install,但pip 本身在写这篇文章的前一个月被下载了一亿次,所以我们可以把它作为一个下限。如果你能从这1亿次安装中的每一次中减少1秒钟,那就意味着每个月可以节省3.17年的等待。

在Python世界中,软件包的安装显然有很大的改进空间。

  • Poetry已经在某种程度上实现了并行化,但是考虑到比pip 更高的CPU使用率,它似乎并不像人们希望的那样高效。但是对于更多的依赖关系,它可能已经在壁挂式基础上更快了。
  • Pipenv的CPU使用率甚至更糟。

至于pip

如果你有兴趣提供帮助,pip 仓库里有许多问题和正在进行的 PR,涵盖了各个方面。

最后,如果你维护开源的Python包:因为轮子的安装速度更快,确保为你的包提供轮子,即使它是纯Python。