Skip to content

Build1 publisher3 min readPublished

uv's hard links cut a 13-second install of 63 packages to 56 milliseconds

A four-way test in a clean Python 3.12 container clocked pip's warm install at 13 seconds and uv's at 56 milliseconds. The speed comes from a filesystem link that holds only while the cache and the environment share a mount.

The Engineer · Build desk

Illustration accompanying uv's hard links cut a 13-second install of 63 packages to 56 milliseconds

What happened

  • One author installed the same requirements four ways, three times each, in a clean Python 3.12 container: pip and uv, each from a cold cache and from a warm cache, into a fresh virtual environment every time.
  • With the cache warm and the environment rebuilt, pip took 13 seconds to install 63 packages including numpy, pandas and pillow; uv took 56 milliseconds.
  • Both installers resolved the same versions, with FastAPI, pandas, numpy, SQLAlchemy and Pydantic checked across the two environments and matching exactly.
  • Forced into copy mode, the state uv falls into when links cannot cross a mount point, uv took 0.32 seconds against pip's 13.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A runner that restores its cache to one mount and builds the virtual environment on another gets uv's copy path, so the install line in a CI budget is fractions of a second per build, not tens of milliseconds.
  • decision Adopting uv for CI has to be argued on the cold figure of roughly five times and the copy-mode figure of 0.32 seconds; the 56 milliseconds buys a faster local rebuild loop for developers.
  • cost The 663MB disk gap only accrues to machines keeping several environments on the same filesystem as the cache; a runner that builds one environment and throws it away collects the speed and almost none of the space.
  • contradiction The post's own two uv timings are 5.7x apart while its prose calls the gap four times, so the copy-mode figure, the one CI actually depends on, carries more uncertainty than the headline pair.

pip's warm install does everything the cold one does except the download. It finds the wheel in the cache, unpacks it, copies the files into the new environment and runs the install machinery, once per package, 63 times [9]. The cost tracks the bytes in the dependency tree, and it is paid on every environment build [9]. uv does different work. It keeps a content-addressed global store and, by default, populates a new environment by hard-linking to files already in that store, so the per-file write is a directory entry and the data stays where it is [10].

Astral, the company behind the Ruff linter, pitches uv at "10 to 100 times faster" than pip [2][3]. Divide 13 seconds by 56 milliseconds and the warm case lands near 232x [1]. That is more than twice the top of the advertised range, and the excess belongs to one machine's filesystem rather than to the resolver. The post is clear about which machine: warm means "the cache was kept but the environment rebuilt, the state your laptop is in all day" [6].

For that ratio to transfer, the cache and the target environment have to sit on the same mount, because links cannot cross a mount point [11]. The tree also has to be heavy enough for copying to dominate, which this one is; it includes numpy, pandas and pillow [1]. With the download cache wiped, the state of a runner with no caching at all, uv came in about five times faster, which the post attributes to parallel downloads against pip's fairly sequential fetching [8].

CI is where the mount condition usually fails. The runner restores the cache onto one mount and builds the environment on another [12]. The post measured that fallback directly by forcing uv into copy mode: 0.32 seconds, against pip's 13 [13]. So a CI budget should be built on 12.68 seconds saved per install [6]. At 50 installs a day, an assumption the reader has to supply from their own pipeline, that is about 10.6 minutes of runner time [7]. The post offers ExtraTime as a site that rebuilds its environment on every push, at 13 to 26 seconds per push on pip [15].

Two of the published figures do not sit together. The post describes copy mode as four times slower than linking [13], but 0.32 seconds against 56 milliseconds is a factor of about 5.7 [2]. The 40x claim against pip does check out, at 40.6 [3]. Each configuration was run three times [5].

The disk claim is the softest of the set. The cache held 225MB of package files, and three environments built from the same requirements added about 12MB between them, roughly 4MB each [14][5]. The comparison figure for three pip environments is 675MB [14], which is exactly three times 225 [4], so the 675 is arithmetic on the cache size; the post did not report installing the three environments with pip.

What to watch

  • A rerun that publishes the per-run times and the absolute cold figures would settle whether copy mode is 4x or 5.7x slower than linking.
  • Whether a stock CI cache restore actually produces the same install path the author got by forcing copy mode by hand.
  • A measured triple pip install, since the 675MB comparison is currently three times the cache size.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories