uv 0.13 Installs 6,000-File Packages 32% Faster
Résumé
Pixel’s announcement explores uv 0.13’s switch to the tar-codec archive backend, which installed a 6,000-file source distribution in a median of 2.53 seconds—about 32% faster than the legacy backend. It also highlights stricter archive validation: unsupported device files are now rejected instead of silently discarded, with an environment variable available to restore legacy behavior.
DEV Community
uv 0.13's tar-codec backend installs a 6,000-file sdist in 2.5 seconds instead of 3.8
I built a source distribution with 6,000 small files in it and installed it with uv pip install. With uv 0.13.6, the median time across five runs was 2.53 seconds. With the same binary told to use the old extraction code, the median was 3.55 seconds. With the actual pre-change release, 0.12.8, it was 3.76 seconds. That's the headline, and it's a real number from a real install, not a microbenchmark of tar extraction in isolation. The part I didn't expect going in was what happened when I fed the new backend a source distribution with a character device node baked into it. The old backend installed it anyway and silently threw the device file away. The new one refuses the whole install and tells you exactly why. uv is Astral's Rust-based replacement for pip, venv and friends. Version 0.13.0, released on 9 October 2026, swapped the library it uses to extract .tar.gz archives from astral-tokio-tar to a newer one called tar-codec. The release notes say the new backend "applies stricter validation when reading archives" and warns that archives with hard links or unsupported tar extensions that older versions accepted may now be rejected. There's an escape hatch: setting UV_LEGACY_TAR_BACKEND=1 restores the old code path. That one environment variable made this easy to test properly, because I could hold the binary constant and flip only the backend. The install-time number I generated a package called bigpkg with 6,000 text files totalling 48MB uncompressed (36MB as a gzipped tarball), gave it a trivial pyproject.toml using the setuptools backend, and installed it with --no-deps into a fresh virtual environment each time, with a fresh cache directory each time. Five runs per configuration: Configuration Run times (s) Median Fastest uv 0.13.6, default (tar-codec) 2.50, 2.52, 2.53, 2.61, 2.75 2.53s 2.50s uv 0.13.6, UV_LEGACY_TAR_BACKEND=1 3.07, 3.46, 3.55, 3.76, 4.84 3.55s 3.07s uv 0.12.8 (pre-change release) 3.21, 3.42, 3.76, 3.93, 5.12 3.76s 3.21s That's a 29% drop in median time against the same binary with the old flag set, and 33% against the actual old release. The fastest-run comparison is less dramatic — 2.50s against 3.07s and 3.21s, around 18-22% — because the slowest outliers in the legacy runs inflate the median more than the minimum. Either way the direction is consistent across all fifteen runs: tar-codec never lost to the legacy path on this workload. I deliberately used a package with many small files rather than one large file, because that's where tar's per-entry overhead shows up. When I first tried this with a package containing 400 files and 8MB of data, the three configurations landed within noise of each other — 0.96 to 2.6 seconds with no clear ordering. The fixed cost of spinning up a Python subprocess to run the setuptools build backend was swamping the extraction time. Scaling up to 6,000 files is what made the effect visible. If you're testing this yourself and see "no difference," that's very likely your workload being too small, not the feature being inert. What it refuses, and what it used to do silently The release notes mention hard links and "unsupported tar extensions." I built four adversarial archives with Python's tarfile module: one with a hard link pointing at ../../../../../../etc/passwd, one with an absolute symlink to /etc/passwd, one with a GNU sparse-file entry missing its required header, and one with a character device node. Archive uv 0.13.6 default uv 0.13.6 legacy flag uv 0.12.8 Hard link escaping the extraction root rejected rejected rejected Absolute symlink rejected rejected rejected Malformed sparse entry rejected rejected rejected Character device node rejected installed installed Three of the four are a wash. This is the finding that argues against my own headline: the path-escape protections for hard links and symlinks aren't new in 0.13. The old backend already refused them, with almost the same wording: # uv 0.12.8 (old backend)
error: Failed to extract archive: file:///....

Powered by Readybot.io