Deduplicatie van alle bestanden in de wheel-cache

Samenvatting

Deze wijziging introduceert deduplicatie op bestandsniveau: elk bestand wordt nu opgeslagen onder zijn BLAKE3-hash in een files-v0 bucket. Deze objecten worden via hardlinks geplaatst op hun oorspronkelijke locaties in archive-v0. Hierdoor verandert de installatiestap niet; de deduplicatie vindt uitsluitend plaats binnen de cache. Bij het opschonen van de cache worden bestandsobjecten verwijderd zodra hun hardlink count tot één daalt.

Impact op opslagruimte

Uit een voorstel (#19694) blijkt de potentiële winst in opslagruimte op basis van verschillende bestandsselecties:

BestandsselectieExtra besparingGehardlinkte bestandenUnieke files-v0 objecten
Executables en native libraries275,7 MiB3.3363.088
Elk payload-bestand $\ge$ 10 MiB235,0 MiB8078
Elk payload-bestand $\ge$ 1 MiB279,7 MiB373349
Elk payload-bestand $\ge$ 100 KiB353,4 MiB2.8302.458
Elk payload-bestand $\ge$ 10 KiB475,7 MiB23.76718.722
Elk payload-bestand $\ge$ 1 KiB537,4 MiB95.15666.422
Alle payload-bestanden545,2 MiB134.22287.129

Op een lokale machine resulteert dit in een besparing van 545,2 MiB, wat ongeveer 10% van de cache is. De tegenprestatie is een vertraging van minder dan 4% bij cold installs (installaties waarbij de cache leeg is), terwijl warm installs (installaties vanuit de cache) niet worden beïnvloed.

Benchmarkresultaten

De optimalisatie is gebenchmarkt tegenover de binary-only cache, waarbij --preview-features content-addressed-cache is ingeschakeld. Onderstaande waarden zijn medianen voor het volledige uv pip install proces; positieve waarden duiden op een vertraging.

WheelCacheParent mediaanGeoptimaliseerde mediaanWijziging (95% CI)
AnyIO 4.9.0cold90,4 ms93,4 ms+3,39% (+2,87% tot +3,86%)
AnyIO 4.9.0warm29,9 ms30,3 ms+1,14% (-1,08% tot +3,85%)
SymPy 1.14.0cold367,1 ms381,6 ms+3,95% (+3,49% tot +4,46%)
SymPy 1.14.0warm84,6 ms84,0 ms-0,64% (-1,04% tot -0,11%)
NumPy 2.2.6cold314,1 ms326,5 ms+3,95% (+3,24% tot +4,82%)
NumPy 2.2.6warm62,5 ms62,7 ms+0,28% (-0,55% tot +1,25%)
PyTorch 2.7.1+cpucold2982,6 ms3072,9 ms+3,03% (+0,98% tot +5,18%)
PyTorch 2.7.1+cpuwarm404,0 ms403,1 ms-0,21% (-0,72% tot +0,11%)

Technische details van de benchmarks

  • Omgeving: Linux/ext4, AMD EPYC-Milan VM (8 CPU's), Python 3.12.13.
  • Build: Rust 1.98.0, geoptimaliseerd profiel zonder LTO.
  • Methode: Installatie van één lokale wheel offline, zonder afhankelijkheden of bytecode-compilatie, gebruikmakend van hardlinks naar een nieuwe virtuele omgeving.
  • Definities: Cold verwijdert de gehele uv-cache; warm behoudt een voorbereide cache. Setup en cleanup zijn niet meegeteld.

Optimalisatie van buffergebruik (#21340)

Wanneer content hashing is ingeschakeld, wordt er momenteel een nieuwe buffer van 64 KiB gealloceerd en genuld voor elk bestand dat tijdens streaming extraction wordt gekopieerd en gehasht. In PR #21340 wordt één buffer hergebruikt voor de gehele wheel.

Bij de PyTorch-wheel vermindert dit het aantal buffer-allocaties voor hashing van 11.120 naar één, terwijl de buffersize op 64 KiB per actieve wheel blijft.

Resultaten van buffer-hergebruik

De volgende metingen vergelijken de initiële wijziging (#21327) met de toegevoegde buffer-optimalisatie bij cold installs:

Package#21327#21327 + buffer reuseWijziging
AnyIO110 ms107 ms-2,6%
SymPy845 ms775 ms-8,3%
NumPy627 ms567 ms-9,5%
PyTorch CPU6,50 s5,99 s-7,8%
14-package env (concurrency 4)6,95 s6,47 s-7,0%

Geëvalueerde alternatieven

Om de prestaties verder te verbeteren, zijn de volgende alternatieven onderzocht:

  • Bestanden opslaan als non-executable: Door executable-flags pas tijdens de installatie toe te passen, konden bestanden tijdens het unzippen direct naar files-v0 worden gehardlinkt. Echter, omdat hardlinks dezelfde modus delen, moeten alle executables alsnog gekopieerd worden. Dit bleek uiteindelijk trager.
  • Pre-fetching van de central directory: Hiermee kon worden bepaald of een bestand executable is op het moment van extractie (momenteel is dit pas bekend ná het unzippen). Dit was trager vanwege de extra overhead van meer HTTP-verzoeken.
  • Heuristieken voor executables: Er is overwogen om te "gokken" of een bestand executable is tijdens extractie en dit indien nodig achteraf te corrigeren. Deze aanpak is verworpen vanwege ontevredenheid over de betrouwbaarheid van de heuristieken.

Concluderend wordt gesteld dat de huidige implementatie, zeker in combinatie met de buffer-optimalisatie uit #21340, een goede balans biedt tussen opslagbesparing en snelheid.