pip vs. uv:Streamlit Cloud 如何将应用加载时间提速 55%

在发现依赖项安装瓶颈后,我们决定尝试使用 Astral uv——这是一个用 Rust 编写的全新的 pip 直接替代品。

发布于 产品,
pip vs. uv: How Streamlit Cloud sped up app load times by 55%

在工程领域,越快越好。设置依赖项和测试代码的速度越快,改进代码的速度也就越快。这种紧密的迭代循环是最终的生产力目标,而速度是 Streamlit 的核心宗旨之一。

应用越复杂,可能需要的依赖项就越多。手动安装依赖项会浪费宝贵的工程资源。这就是包管理器存在的原因。 

Python 中最流行的包管理器是“首选安装程序”(Preferred Installer Program)——也就是“pip”。然而,虽然 pip 可能是最流行的包管理器,但它并非总是最快的。最快的工具原来是 uv。  

当我们将 pip 切换到 uv 包管理器后,我们将 Streamlit Cloud 应用部署时间加快了 55%! 

在这篇博客中,你将了解我们是如何发现需要更快的应用加载时间,以及最终为何选择 uv 而非 pip 作为 Streamlit 首选的包管理器。

发现需要更快的应用加载时间 

在开发 Streamlit Community Cloud 时,我们进行了大量的测试部署。这意味着我们花费了大量时间盯着那个点心加载动画。那时我们意识到:如果我们要等超过 10 秒,那么我们的用户也是。😨

那么为什么会出现这种延迟呢?一切都归结于“冷部署”(cold deployments)。

在本地开发期间,你可以使用 Streamlit 的热重载(hot-reload)功能,让你能立即看到代码的更改,从而更快地迭代代码。Streamlit 在更新 Community Cloud 上运行的应用时使用了类似的机制。在这两种情况下,所有应用的依赖项都已通过 pip 安装,无需额外的包处理工作。这使得应用能够快速加载更改。 

然而,当 Streamlit 应用“进入睡眠”并需要被“唤醒”时,就会出现延迟。唤醒一个睡眠的应用需要冷部署。在冷部署中,Streamlit Community Cloud 会执行额外的工作来配置执行环境并进行初始设置,以便应用可以运行。结果就是漫长的加载时间。

一个生产就绪的应用需要快速可靠,所以我们知道必须加快冷部署的速度。我们不知道的是,部署瓶颈究竟出在哪里。于是——我们开始收集指标。

那时我们发现,部署在依赖项安装阶段显著变慢了——更重要的是,我们的大多数用户都使用 pip 来安装 Python 依赖项。

2024年4月初: 

  • 平均每日部署时间约为 90 秒
  • 安装依赖项占用了大部分时间,平均约为 60 秒
  • 在所有部署中,97% 使用了 pip 

于是我们问自己:“如何才能让它更快?”

是什么让 pip 如此缓慢?稳定性与速度的权衡

自 2008 年诞生以来,pip 已成为 Python 官方且主要的包管理器。它与 CPython 打包在一起,并且相对易于使用,使其成为首选的无忧解决方案。 

由于其流行性,pip 需要提供卓越的稳定性。因此,速度是次要的考量。事实上,自 2013 年以来,pip 在 GitHub 上最古老的开放问题之一就围绕着并行下载包的需求。多年来,pip 的主要关注点是如何在不影响用户体验的情况下实现确定性。

为什么 pip 安装所需包需要这么长时间?这个过程可以分为两个主要步骤:

  1. pip 确定要下载和安装哪些包。 这意味着解析用户提供的依赖项列表,以确定需要下载的内容。(以 Streamlit 为例,这会导致大约 200 次不同大小的网络调用——包括数兆字节的下载!) 
  2. pip 安装必要的包。 这包括解压它们,将它们放置在正确的位置,以及预编译模块。

所有这些操作都是顺序执行的,而且是在单个线程上。你可能已经能想象到,所有这些往返时间 (RTT)、传输时间和 I/O 是如何累积起来的——在你意识到之前,几分钟已经过去了,而你的应用仍然远未准备就绪!

这就是稳定性与速度之间的权衡。对于 Streamlit 来说,速度至关重要,所以我们寻找了替代方案。 

uv 来救援

2024年4月,我们发现了 Astral 出品的 uv Python 包管理器。💥

什么是uv 包管理器 根据其 GitHub 页面介绍,uv 是“一个用 Rust 编写的极其快速的 Python 包安装程序和解析器。旨在作为常见 pip 和 pip-tools 工作流程的直接替代品。”

uv 如此之快,是因为它大量使用了并行化、缓存以及许多其他巧妙的技巧。 

例如,当决定下载和安装哪些包时,pip 首先会下载每个包的依赖项列表。对于 Python wheel 包,这个列表包含在元数据文件中。为了访问这些元数据,pip 必须下载整个 wheel 文件,即使只需要元数据。

uv 采用了一种不同的方法。因为 wheel 文件是 zip 归档文件,uv 仅查询索引(也称为中央目录),并利用其中包含的文件偏移量来仅下载元数据文件。uv 消除了为了访问元数据文件而下载整个 Python wheel 文件的非必要步骤。

那么 uv 到底有多快?根据 Astral 的说法,uv 比 pip 快 8-10 倍。

2024年4月中旬,我们将 Streamlit Community Cloud 上所有使用 pip 的用户切换到 uv,使得平均依赖项安装时间从 60 秒降至 20 秒。

A bar graph illustrating how both total deployment and dependency install times decreased once uv was introduced.
一张柱状图,展示了引入 uv 后总部署时间和依赖项安装时间如何下降。

这意味着总平均启动时间下降了 55%——从 90 秒降至 40 秒。我觉得还不错!

一个视频,比较了左侧使用 pip 和右侧使用 uv 加载 Streamlit 应用的情况。

开源让事情变得更快

uv 提供的加速包管理等改进是通过开源开发实现的。当一切都公开透明时,每个人都有机会贡献并帮助解决问题。我们感谢 uv 的开发者,这样我们就可以为社区提供更快的应用启动时间!

如果你已经是 Streamlit Community Cloud 用户,无需额外操作即可使用 uv。 

对于本地开发,你可以立即开始使用 uv:

  1. 根据uv 文档安装 uv
  2. 使用 uv pip install 代替 pip install

在提升 Streamlit Community Cloud 加载速度方面,这仅仅是个开始。预计很快会有更多改进!

要了解更多关于在其他 Python 项目中使用 uv 的信息,请查阅文档,并告诉我们你的体验。你可以在发布说明中随时了解 Streamlit 的最新动态。

快乐地(更快地!)使用 Streamlit!

分享此文章

评论

在我们的论坛继续交流 →

也在产品分类下...

查看更多 →