uv: Deduplicate all files in the wheel cache

uv: Deduplicate all files in the wheel cache

On main, we support content-addressed caching, but only at the wheel-level. That is, if you download the same wheel twice from different sources, they share a cache entry. But files within or across wheels are not deduplicated at all. 在主分支上,我们支持基于内容寻址的缓存,但仅限于 Wheel 级别。也就是说,如果你从不同来源下载同一个 Wheel 两次,它们会共享一个缓存条目。但 Wheel 内部或跨 Wheel 的文件并没有进行任何去重。

This PR adds deduplication at the file level: every file is now stored under its BLAKE3 hash in a files-v0 bucket. We hardlink these objects into their original locations in archive-v0, so the installation step doesn’t change at all — we’re just deduping within the cache (and cache cleanup removes file objects when their hardlink count drops to one). 此 PR 在文件级别增加了去重功能:现在每个文件都以其 BLAKE3 哈希值存储在 files-v0 存储桶中。我们将这些对象硬链接到 archive-v0 中的原始位置,因此安装步骤完全没有改变——我们只是在缓存内部进行去重(当硬链接计数降至一时,缓存清理程序会移除相应的文件对象)。

In the prior proposal (#19694), we included the following table: 在之前的提案 (#19694) 中,我们包含了下表:

File selectionAdditional savingsFiles hardlinkedDistinct files-v0 objects
Executables and native libraries275.7 MiB3,3363,088
Any payload file ≥ 10 MiB235.0 MiB8078
Any payload file ≥ 1 MiB279.7 MiB373349
Any payload file ≥ 100 KiB353.4 MiB2,8302,458
Any payload file ≥ 10 KiB475.7 MiB23,76718,722
Any payload file ≥ 1 KiB537.4 MiB95,15666,422
All payload files545.2 MiB134,22287,129
文件选择额外节省空间硬链接文件数不同的 files-v0 对象数
可执行文件和原生库275.7 MiB3,3363,088
任意 ≥ 10 MiB 的载荷文件235.0 MiB8078
任意 ≥ 1 MiB 的载荷文件279.7 MiB373349
任意 ≥ 100 KiB 的载荷文件353.4 MiB2,8302,458
任意 ≥ 10 KiB 的载荷文件475.7 MiB23,76718,722
任意 ≥ 1 KiB 的载荷文件537.4 MiB95,15666,422
所有载荷文件545.2 MiB134,22287,129

So we’re saving 545.2 MiB on my local machine, or about 10% of the cache. In return, the net effect seems to be something like a <4% slowdown for cold installs (and no effect on warm installs), which I think is probably worthwhile here. 因此,在我的本地机器上节省了 545.2 MiB,约占缓存的 10%。作为交换,其净影响似乎是冷安装速度下降不到 4%(对热安装无影响),我认为这在这里是值得的。


N.B. Slop comment for benchmark data. We benchmarked the optimization in 85c3f7485 against the binary-only cache at a3f977ece, with --preview-features content-addressed-cache enabled on both. These are medians for the complete uv pip install process; positive changes mean slower. 注:基准测试数据的补充说明。我们针对 85c3f7485 中的优化与 a3f977ece 处的仅二进制缓存进行了基准测试,两者均启用了 --preview-features content-addressed-cache。这些是完整 uv pip install 过程的中位数;正值表示变慢。

Wheel CacheParent medianOptimized medianChange (95% CI)
AnyIO 4.9.0 cold90.4 ms93.4 ms+3.39% (+2.87% to +3.86%)
AnyIO 4.9.0 warm29.9 ms30.3 ms+1.14% (-1.08% to +3.85%)
SymPy 1.14.0 cold367.1 ms381.6 ms+3.95% (+3.49% to +4.46%)
SymPy 1.14.0 warm84.6 ms84.0 ms-0.64% (-1.04% to -0.11%)
NumPy 2.2.6 cold314.1 ms326.5 ms+3.95% (+3.24% to +4.82%)
NumPy 2.2.6 warm62.5 ms62.7 ms+0.28% (-0.55% to +1.25%)
PyTorch 2.7.1+cpu cold2982.6 ms3072.9 ms+3.03% (+0.98% to +5.18%)
PyTorch 2.7.1+cpu warm404.0 ms403.1 ms-0.21% (-0.72% to +0.11%)

Before this optimization, the measured cold regressions were +4.02% for AnyIO 4.9.0, +19.41% for SymPy 1.14.0, +12.75% for NumPy 2.2.6, +15.30% for PyTorch 2.7.1+cpu. All eight median regressions are below 5%. The PyTorch cold 95% interval still reaches 5.18%, so its upper bound is not below 5%. Every other upper bound is below 5%. 在本次优化之前,测得的冷安装回归数据为:AnyIO 4.9.0 为 +4.02%,SymPy 1.14.0 为 +19.41%,NumPy 2.2.6 为 +12.75%,PyTorch 2.7.1+cpu 为 +15.30%。目前所有八项中位数回归均低于 5%。PyTorch 冷安装的 95% 置信区间仍达到 5.18%,因此其上限未低于 5%。其余所有上限均低于 5%。

We retained all 1,860 measured installs, including outliers, and combined every confirmation run for this candidate. Cold measurements have 360 paired rounds for AnyIO, 120 each for SymPy and NumPy, and 90 for PyTorch; warm measurements have 60 paired rounds per wheel. Baseline/candidate order alternates, with three warmups per case. Intervals use a paired percentile bootstrap of the ratio of medians with 10,000 resamples. 我们保留了所有 1,860 次测量安装(包括异常值),并合并了该候选版本的所有确认运行数据。冷安装测量中,AnyIO 有 360 轮配对测试,SymPy 和 NumPy 各 120 轮,PyTorch 90 轮;热安装每种 Wheel 有 60 轮配对测试。基准/候选顺序交替进行,每种情况有三次预热。置信区间使用 10,000 次重采样的中位数比率配对百分位自助法计算。

These measurements ran on Linux/ext4, an AMD EPYC-Milan VM pinned to eight CPUs, with Python 3.12.13. Both binaries use Rust 1.98.0 and the same profiling profile (optimized, no LTO). Each sample installs one pinned local wheel offline, without dependencies or bytecode compilation, using hardlinks into a fresh virtual environment. Cold removes the entire uv cache; warm retains a primed cache. Setup and cleanup are untimed, and the wheel OS page cache is warm. No compilation ran during the benchmarks. 这些测量在 Linux/ext4 环境下运行,使用绑定到 8 个 CPU 的 AMD EPYC-Milan 虚拟机,Python 版本为 3.12.13。两个二进制文件均使用 Rust 1.98.0 和相同的分析配置文件(已优化,无 LTO)。每个样本在离线状态下安装一个固定的本地 Wheel,不包含依赖项或字节码编译,使用硬链接安装到全新的虚拟环境中。冷安装会移除整个 uv 缓存;热安装则保留预热后的缓存。设置和清理过程不计入时间,Wheel 的操作系统页面缓存处于预热状态。基准测试期间未进行任何编译。

These results do not cover other platforms or network-inclusive installs. All-file deduplication, content/executable identities, the cache layout, complete archives, and copy fallbacks are preserved. Inode checks confirmed that every archived file shares its file-store object for all four wheels. The five targeted integration tests passed ten stress iterations (50 executions), including local and streamed wheels with one and four workers, RECORD handling, cache cleanup, and cross-filesystem installation. Formatting and Clippy with warnings denied also passed. 这些结果不涵盖其他平台或包含网络的安装。全文件去重、内容/可执行文件标识、缓存布局、完整归档和复制回退机制均已保留。Inode 检查确认,所有四个 Wheel 的每个归档文件都共享其文件存储对象。五个针对性的集成测试通过了十次压力迭代(共 50 次执行),包括使用一个和四个工作进程的本地及流式 Wheel 安装、RECORD 处理、缓存清理以及跨文件系统安装。格式化检查和开启拒绝警告的 Clippy 测试也已通过。