For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /developers/blog/2020-07-13-alpine-python.md.

运行 Home Assistant 时提升 Python 速度 40%

我们的大多数 Containers 使用 Alpine。它是容器的完美发行版,因为它体积小(基于 BusyBox)、支持许多 CPU 架构,并且包系统精简。Alpine 使用 musl 作为其 C 库,而不是更常用的 glibc。

Alpine 与 musl 相比其同类产品相对年轻(分别有 15 年和 9 年的历史),但发展速度显著。由于进展如此之快,基于已不再真实的情况,对两者存在许多误解。本文的目标是解决其中几个问题,以及我们如何解决它们。

本博文无意成为 musl vs. glibc 的争吵。每种用例都不同,有自己的权衡。例如,我们在操作系统中使用 glibc。

对于测试,我使用了 Docker Python library 的镜像,结果发布到我们的 base images。我使用 pyperformance 进行实验室测试,并使用 Home Assistant 内部 benchmark 工具进行更真实的对比。测试环境运行在与 Docker host 相同的容器内。

C/POSIX 标准库

我经常读到:Python 在使用 musl 作为默认 C 库时速度较慢。这个事实并不 100% 正确。如果 Python 运行时使用相同的 GCC 和 -O3 编译,glibc 版本在实验室基准测试中稍快,但在实际世界中,差异微不足道。Alpine 使用 -Os 编译,而大多数其他发行版使用 -O2 编译。这导致了 Python 运行时解释器之间常被提及的差异。但使用相同的编译器优化时,基于 musl 的 Python 运行时没有负面副作用。

但有一个变革者,使得基于 musl 的运行时相比基于 glibc 的运行时更实用。它是内存分配器 jemalloc,一个注重减少碎片和可扩展并发支持的通用 malloc 实现。我在一些关于 Rust 的博文上发现了一个有趣的效果。一些开发者发现 musl 在使用 jemalloc 时比 glibc 快得多,而 glibc 在使用 jemalloc 时反而变慢。可以肯定的是,glibc 和 jemalloc 的好处不在于速度(因为它们优化的是内存管理),但 musl 同时获得两者的好处。虽然纯 musl 和 glibc 之间的差异可以忽略不计,但 musl + jemalloc 与 glibc 之间的差异是实质性的(禁用 GCC 内置内存分配器优化时)。是的,现在的 jemalloc 与 musl 兼容(曾经有一段时间不兼容)。

编译器

如何编译 Python 也很重要。Fedora 或 Redhat 曾有关于禁用 semantic-interposition 以获得高性能提升的声明。我在 GCC 9.3.0 上无法重现这一点,但也未看到任何不良副作用。我建议禁用语义(如内置分配器优化)并在构建时链接 jemalloc。我也建议使用 -O3 优化。我们从未在目标平台上看到这些激进优化引发任何问题。需要说明的是,与发行版的 Python 运行时解释器不同,我们不需要在所有地方运行。因此,我们可以不加任何覆盖地使用 --enable-optimizations 并添加更多 flags。我可以肯定地说,PGO/LTO/O3 使 Python 更快,并且在我们的目标 CPU 上有效。

Python 包

Alpine 使用 musl 确实没有 manylinux 兼容性。如果你不缓存构建,安装需要 C extensions 的包时,需要编译它们。这个过程需要时间,就像使用 Qemu 为不同 CPU 架构 cross-build 一样。你无法从 PyPi 获取预编译的二进制文件。这对我们不是问题,因为 PyPI 上提供的二进制文件大多不为我们的目标系统优化。

为了解决 Python 包的安装时间问题,我们创建了自己的 wheel indexbackend,用于编译所有需要的 wheels,并通过 CI agents 保持最新。我们为每种 CPU 架构预构建了超过 1k 个包,Docker 文件的构建时间已不再重要。

Alpine Linux

Alpine 是容器的优秀基础系统,使我们能够为用户提供最佳体验。非常感谢 Alpine Linux、musl 和 jemalloc,使这一切成为可能。

下表显示了 Alpine Linux 的 Python 运行时与我们优化(GCC 9.3.0/musl)的结果对比。所有测试均使用 Python 3.8.3 进行。

BenchmarkAlpineOptimized
2to3924 ms699 ms: 1.32x faster (-24%)
chameleon37.9 ms25.6 ms: 1.48x faster (-33%)
chaos393 ms273 ms: 1.44x faster (-31%)
crypto_pyaes373 ms245 ms: 1.52x faster (-34%)
deltablue22.8 ms16.4 ms: 1.39x faster (-28%)
django_template184 ms145 ms: 1.27x faster (-21%)
dulwich_log157 ms122 ms: 1.29x faster (-22%)
fannkuch1.81 sec1.32 sec: 1.38x faster (-27%)
float363 ms263 ms: 1.38x faster (-28%)
genshi_text113 ms83.9 ms: 1.34x faster (-26%)
genshi_xml226 ms171 ms: 1.32x faster (-24%)
go816 ms598 ms: 1.36x faster (-27%)
hexiom36.8 ms24.2 ms: 1.52x faster (-34%)
json_dumps34.8 ms25.6 ms: 1.36x faster (-26%)
json_loads61.2 us47.4 us: 1.29x faster (-23%)
logging_format30.0 us23.5 us: 1.28x faster (-22%)
logging_silent673 ns486 ns: 1.39x faster (-28%)
logging_simple27.2 us21.3 us: 1.27x faster (-22%)
mako54.5 ms35.6 ms: 1.53x faster (-35%)
meteor_contest344 ms219 ms: 1.57x faster (-36%)
nbody526 ms305 ms: 1.73x faster (-42%)
nqueens368 ms246 ms: 1.49x faster (-33%)
pathlib64.4 ms45.2 ms: 1.42x faster (-30%)
pickle20.3 us17.1 us: 1.19x faster (-16%)
pickle_dict40.2 us33.6 us: 1.20x faster (-16%)
pickle_list6.77 us5.88 us: 1.15x faster (-13%)
pickle_pure_python1.85 ms1.27 ms: 1.45x faster (-31%)
pidigits274 ms222 ms: 1.24x faster (-19%)
pyflate2.53 sec1.74 sec: 1.45x faster (-31%)
python_startup14.9 ms12.1 ms: 1.23x faster (-19%)
python_startup_no_site9.84 ms8.24 ms: 1.19x faster (-16%)
raytrace1.61 sec1.23 sec: 1.30x faster (-23%)
regex_compile547 ms398 ms: 1.38x faster (-27%)
regex_dna445 ms484 ms: 1.09x slower (+9%)
regex_effbot10.3 ms9.96 ms: 1.03x faster (-3%)
regex_v881.8 ms71.6 ms: 1.14x faster (-12%)
richards265 ms182 ms: 1.46x faster (-31%)
scimark_fft1.31 sec851 ms: 1.54x faster (-35%)
scimark_lu616 ms384 ms: 1.61x faster (-38%)
scimark_monte_carlo390 ms248 ms: 1.57x faster (-36%)
scimark_sor838 ms571 ms: 1.47x faster (-32%)
scimark_sparse_mat_mult19.0 ms13.2 ms: 1.43x faster (-30%)
spectral_norm567 ms388 ms: 1.46x faster (-32%)
sqlalchemy_declarative364 ms286 ms: 1.27x faster (-21%)
sqlalchemy_imperative60.3 ms46.8 ms: 1.29x faster (-22%)
sqlite_synth6.88 us5.09 us: 1.35x faster (-26%)
sympy_expand1.39 sec1.05 sec: 1.32x faster (-24%)
sympy_integrate67.3 ms49.5 ms: 1.36x faster (-26%)
sympy_sum505 ms389 ms: 1.30x faster (-23%)
sympy_str945 ms656 ms: 1.44x faster (-31%)
telco17.9 ms12.5 ms: 1.44x faster (-31%)
tornado_http347 ms273 ms: 1.27x faster (-21%)
unpack_sequence232 ns212 ns: 1.09x faster (-9%)
unpickle41.6 us30.7 us: 1.36x faster (-26%)
unpickle_list10.5 us9.24 us: 1.14x faster (-12%)
unpickle_pure_python1.28 ms945 us: 1.36x faster (-26%)
xml_etree_parse335 ms292 ms: 1.15x faster (-13%)
xml_etree_iterparse281 ms226 ms: 1.24x faster (-20%)
xml_etree_generate330 ms219 ms: 1.51x faster (-34%)
xml_etree_process263 ms181 ms: 1.45x faster (-31%)