19 ms·
How uv got so fast
- yjftsjthsd-h 10mo ago> No bytecode compilation by default. pip compiles .py files to .pyc during installation. uv skips this step, shaving time off every install. You can opt in if you want it. Are we losing out on performance of the actual installed thing, then? (I'm not 100% clear on .pyc files TBH; I'm guessing they speed up start time?)
- woodruffw 10mo agoNo, because Python itself will generate bytecode for packages once you actually import them. uv just defers that to first-import time, but the cost is amortized in any setting where imports are performed over multiple executions.
- yjftsjthsd-h 10mo agoThat sounds like yes? Instead of doing it once at install time, it's done once at first use. It's only once so it's not persistently slower, but that is a perf hit. My first cynical instinct is to say that this is uv making itself look better by deferring the costs to the application, but it's probably a good trade-off if any significant percentage of the files being compiled might not be used ever so the overall cost is lower if you defer to run time.
- woodruffw 10mo ago> It's only once so it's not persistently slower, but that is a perf hit. Sure, but you pay that hit either way. Real-world performance is always usage based: the assumption that uv makes is that people run (i.e. import) packages more often than they install them, so amortizing at the point of the import machinery is better for the mean user. (This assumption is not universal, naturally!)
- dddgghhbbfblk 10mo agoUmmm, your comment is backwards, right?
- woodruffw 10mo agoWhich part? The assumption is that when you `$TOOL install $PACKAGE`, you run (i.e. import) `$PACKAGE` more than you re-install it. So there's no point in slowing down (relatively less common) installation events when you can pay the cost once on import. (The key part being that 'less common' doesn't mean a non-trivial amount of time.)
- dddgghhbbfblk 10mo agoWhy would you want to slow down the more common thing instead of the less common thing? I'm not following that at all. That's why I asked if that's backwards.
- beacon294 10mo agoProbably for any case where an actual human is doing it. On an image you obviously want to do it at bake time, so I feel default off with a flag would have been a better design decision for pip. I just read the thread and use Python, I can't comment on the % speedup attributed to uv that comes from this optimization.
- Epa095 10mo agoImages are a good example where doing it at install-time is probably the best yeah, since every run of the image starts 'fresh', losing the compilation which happened last time the image got started. If it was a optional toggle it would probably become best practice to activate compilation in dockerfiles.
- zahlman 10mo ago> On an image you obviously want to do it at bake time It seems like tons of people are creating container images with an installer tool and having it do a bunch of installations, rather than creating the image with the relevant Python packages already in place. Hard to understand why. For that matter, a pre-baked Python install could do much more interesting things to improve import times than just leaving a forest of `.pyc` files in `__pycache__` folders all over the place.
- saidnooneever 10mo agoyou are right. it depends on how often this first start is, if its bad or not..most usecases id guess (total guess, have limited exp with python projects professionally) its not an issue.
- tedivm 10mo agoYou can change it to compile the bytecode on install with a simple environment variable (which you should do when building docker containers if you want to sacrifice some disk space to decrease initial startup time for your app).
- VorpalWay 10mo agoI think they are making the bet that most modules won't be imported. For example if I install scipy, numpy, Pillow or such: what are the chances that I use a subset of the modules vs literally all of them? I would bet on a subset for pretty much any non-trivial package (i.e. larger than one or two user facing modules). And for those trivial packages? Well they are usually small, so the cost is small as well. I'm sure there are exceptions: maybe a single gargantuan module thst consists of autogenerated FFI bindings for some C library or such, but that is likely the minority.
- zmgsabst 10mo agoThat’s actually a negative: My Docker build generating the byte code saves it to the image, sharing the cost at build time across all image deployments — whereas, building at first execution means that each deployed image instance has to generate its own bytecode! That’s a massive amplification, on the order of 10-100x. “Well just tell it to generate bytecode!” Sure — but when is the default supposed to be better? Because this sounds like a massive footgun for a system where requests >> deploys >> builds. That is, every service I’ve written in Python for the last decade.
- salviati 10mo agoHistorically the practice of producing pyc files on install started with system wide installed packages, I believe, when the user running the program might lack privileges to write them. If the installer can write the .oy files it can also write the .pyc, while the user running them might not in that location.
- plorkyeran 10mo agoIf you have a dependency graph large enough for this to be relevant, it almost certainly includes a large number of files which are never actually imported. At worst the hit to startup time will be equal to the install time saved, and in most cases it'll be a lot smaller.
- zahlman 10mo ago> a large number of files which are never actually imported Unfortunately, it typically doesn't work out as well as you might expect, especially given the expectation of putting `import` statements at the top of the file.
- hauntsaninja 10mo agoYes, uv skipping this step is a one time significant hit to start up time. E.g. if you're building a Dockerfile I'd recommend setting `--compile-bytecode` / `UV_COMPILE_BYTECODE`
- thundergolfer 10mo agoThis optimization hits serverless Python the worst. At Modal we ensure users of uv are setting UV_COMPILE_BYTECODE to avoid the cold start penalty. For large projects .pyc compilation can take hundreds of milliseconds.
- zahlman 10mo ago> I'm not 100% clear on .pyc files TBH; I'm guessing they speed up start time? They do. > Are we losing out on performance of the actual installed thing, then? When you consciously precompile Python source files, you can parallelize that process. When you `import` from a `.py` file, you only get that benefit if you somehow coincidentally were already set up for `multiprocessing` and happened to have your workers trying to `import` different files at the same time.
- blintz 10mo ago> PEP 658 went live on PyPI in May 2023. uv launched in February 2024. The timing isn’t coincidental. uv could be fast because the ecosystem finally had the infrastructure to support it. A tool like uv couldn’t have shipped in 2020. The standards weren’t there yet. How/why did the package maintainers start using all these improvements? Some of them sound like a bunch of work, and getting a package ecosystem to move is hard. Was there motivation to speed up installs across the ecosystem? If setup.py was working okay for folks, what incentivized them to start using pyproject.toml?
- yjftsjthsd-h 10mo agoBecause static declaration was clearly safer and more performant? My question is why pip isn't fully taking advantage
- eesmith 10mo agoBecause pip contains decades of built-up code and lacks the people willing to work on updating it.
- zahlman 10mo ago> If setup.py was working okay for folks, what incentivized them to start using pyproject.toml? It wasn't working okay for many people, and many others haven't started using pyproject.toml. For what I consider the most egregious example: Requests is one of the most popular libraries, under the PSF's official umbrella, which uses only Python code and thus doesn't even need to be "built" in a meaningful sense. It has a pyproject.toml file as of the last release. But that file isn't specifying the build setup following PEP 517/518/621 standards. That's supposed to appear in the next minor release, but they've only done patch releases this year and the relevant code is not at the head of the repo, even though it already caused problems for them this year. It's been more than a year and a half since the last minor release.
- zahlman 10mo agoI should have mentioned one of the main reasons setup.py turns out not okay for people (aside from the general unpleasantness of running code to determine what should be, and mostly is, static metadata): in the legacy approach, Setuptools has to get `import`ed from the `setup.py` code before it can run, but running that code is the way to find out the dependencies. Including build-time dependencies. Specifically Setuptools itself. Good luck if the user's installed version is incompatible with what you've written.
- woodruffw 10mo agoI think this post does a really good job of covering how multi-pronged performance is: it certainly doesn't hurt uv to be written in Rust, but it benefits immensely from a decade of thoughtful standardization efforts in Python that lifted the ecosystem away from needing `setup.py` on the hot path for most packages.
- yjftsjthsd-h 10mo agoI think a lot of rust rewrites have this benefit; if you start with hindsight you can do better more easily. Of course, rust is also often beneficial for its own sake, so it's a one-two punch:)
- woodruffw 10mo agoCompletely agreed!
- pxc 10mo agoSuccinctly, perhaps with some loss of detail: "Rewrite" is important as "Rust".
- s_ting765 10mo agoRust rewrites are known for breaking (compatibility with) working software. That's all there is to them.
- pxc 10mo agoIn Python's case, as the article describes quite clearly, the issue is that the design of "working software" (particularly setup.py) was bad to the point of insane (in much the same way as the NPM characteristics that enabled the recent Shai Hulud supply chain attacks, but even worse). At some point, compatibility with insanity has got to go. Helpfully, though, uv retains compatibility with newer (but still well-established) standards in the Python community that don't share this insanity!
- nurettin 10mo ago> When a package says it requires python<4.0, uv ignores the upper bound and only checks the lower. I will bring popcorn on python 4 release date.
- yjftsjthsd-h 10mo agoIf it's really not doing any upper bound checks, I could see it blowing up under more mundane conditions; Python includes breaking changes on .x releases, so I've had eg. packages require (say) Python 3.10 when 3.11/12 was current.
- dev_l1x_be 10mo agoI always bring popcorn on major version changes for any programming language. I hope Rust's never 2.0 stance holds.
- zahlman 10mo agoIt would be popcorn-worthy regardless, given the rhetoric surrounding the idea in the community.
- ethin 10mo ago> Zero-copy deserialization. uv uses rkyv to deserialize cached data without copying it. The data format is the in-memory format. This is a Rust-specific technique. This (zero-copy deserialization) is not a rust-specific technique, so I'm not entirely sure why the author describes it as one. Any good low level language (C/C++ included) can do this from my experience.
- nemothekid 10mo agoGiven the context of the article, I think "Rust specific" here means that "it couldn't be done in python". For example "No interpreter startup" is not specific to Rust either.
- kbd 10mo agoIt's Rust vs Python in this case.
- woodruffw 10mo agoI think the framing in the post is that it's specific to Rust, relative to what Python packaging tools are otherwise written in (Python). It's not very easy to do zero-copy deserialization in pure Python, from experience. (But also, I think Rust can fairly claim that it's made zero-copy deserialization a lot easier and safer.)
- stefan_ 10mo agoI suppose it can fairly claim that now every other library and blog post invokes "zero-copy" this and that, even in the most nonsensical scenarios. It's a technique for when you can literally not afford the memory bandwidth, because you are trying to saturate a 100Gbps NIC or handling 8k 60Hz video, not for compromising your data serialization schemes portability for marketing purposes while all applications hit the network first, disk second and memory bandwidth never.
- woodruffw 10mo agoMany of the hot paths in uv involve an entirely locally cached set of distributions that need to be loaded into memory, very lightly touched/filtered, and then sunk to disk somewhere else. In those contexts, there are measurable benefits to not transforming your representation. (I'm agnostic on whether zero-copy "matters" in every single context. If there's no complexity cost, which is what Rust's abstractions often provide, then it doesn't really hurt.)
- agumonkey 10mo agovery nice article, always good to get a review of what a "simple" looking tool does behind the scense about rust though some say a nicer language helps finding the right architecture (heard that about cpp veteran dropping it for ocaml, any attempted idea would take weeks in cpp, was a few days in ocaml, they could explore more) also the parallelism might be a benefit the language orientation enough semi fanboyism
- aswegs8 10mo agouv seems to be a pet peeve of HN. I always thought pipenv was good but yeah, seems like I was being ignorant
- aw1621107 10mo ago> uv seems to be a pet peeve of HN. Unless I've been seeing very different submissions than you, "pet peeve" seems like the exact opposite of what is actually the case?
- VerifiedReports 10mo agoIndeed; I don't think he knows what "peeve" means...
- glaucon 10mo agoI too use pipenv unless there's a reason not to. I hope people use whatever works best for them. I feel that sometimes there's a desire on the part of those who use tool X that everyone should use tool X. For some types of technology (car seat belts, antibiotics...) that might be reasonable but otherwise it seems more like a desire for validation of the advocate's own choice.
- EdwardDiego 10mo agoMy biggest complaint with pipenv is/was(?) that it's lockfile format only kept the platform identifiers of the platform you locked it on - so if you created it on Mac, then tried to install from the lockfile on a Linux box, you're building from source because it's only locked in wheels for MacOS. Poetry and uv avoid this issue.
- jlubawy 10mo agoCame here to ask about pipenv. As someone who does not use python other than for scripting, but also appreciates the reproduceability that pipenv provides, should I be using uv? My understanding is that pipenv is the better successor to venv and pip (combined), but now everyone is talking about uv so to be honest it's quite confusing. Edit: to add to what my understanding of pipenv is, the "standard/approved" method of package management by the python community, but in practice is it not? Is it now uv?
- epage 10mo ago> uv is fast because of what it doesn’t do, not because of what language it’s written in. The standards work of PEP 518, 517, 621, and 658 made fast package management possible. Dropping eggs, pip.conf, and permissive parsing made it achievable. Rust makes it a bit faster still. Isn't assigning out what all made things fast presumptive without benchmarks? Yes, I imagine a lot is gained by the work of those PEPs. I'm more questioning how much weight is put on dropping of compatibility compared to the other items. There is also no coverage for decisions influenced by language choice which likely influences "Optimizations that don’t need Rust". This also doesn't cover subtle things. Unsure if rkyv is being used to reduce the number of times that TOML is parsed but TOML parse times do show up in benchmarks in Cargo and Cargo/uv's TOML parser is much faster than Python's (note: Cargo team member, `toml` maintainer). I wish the TOML comparison page was still up and showed actual numbers to be able to point to.
- zahlman 10mo ago> Isn't assigning out what all made things fast presumptive without benchmarks? We also have the benchmark of "pip now vs. pip years ago". That has to be controlled for pip version and Python version, but the former hasn't seen a lot of changes that are relevant for most cases, as far as I can tell. > This also doesn't cover subtle things. Unsure if rkyv is being used to reduce the number of times that TOML is parsed but TOML parse times do show up in benchmarks in Cargo and Cargo/uv's TOML parser is much faster than Python's (note: Cargo team member, `toml` maintainer). I wish the TOML comparison page was still up and showed actual numbers to be able to point to. This is interesting in that I wouldn't expect that the typical resolution involves a particularly large quantity of TOML. A package installer really only needs to look at it at all when building from source, and part of what these standards have done for us is improve wheel coverage. (Other relevant PEPs here include 600 and its predecessors.) Although that has also largely been driven by education within the community, things like e.g. https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html https://blog.ganssle.io/articles/2021/10/setup-py-deprecated... and https://pradyunsg.me/blog/2022/12/31/wheels-are-faster-pure-python/ https://pradyunsg.me/blog/2022/12/31/wheels-are-faster-pure-... .
- 10mo ago
- pecheny 10mo agoThe content is nice and insightful! But God I wish people stopped using LLMs to 'improve' their prose... Ironically, some day we might employ LLMs to re-humanize texts that had been already massacred.
- laidoffamazon 10mo agoInterestingly I didn’t catch this, I liked it for not looking LLM written!
- yunohn 10mo ago“Why this matters” being the final section is a guaranteed give away, among innumerable others.
- rick_dalton 10mo agoI realized once I was in the "optimizations that dont need rust" section. Specifically "This is concurrency, not language magic."
- dkmar 10mo agoYup. The author has now swapped that part out for “Any language can do this.” Just commenting to preempt any comments telling you that the article doesn’t say this.
- yunohn 10mo agoI used to rely on this, and still mostly do - but you’d be surprised how quickly this has entered the normal vernacular! I hear people using it in conversation unprompted all the time.
- yunohn 10mo agoI have reached a point where any AI smell (of which this articles has many) makes me want to exit immediately. It feels tortuous to my reading sensibilities. I blame fixed AI system prompts - they forcibly collapse all inputs into the same output space. Truly disappointing that OpenAI et all have no desire to change this before everything on the internet sounds the same forever.
- ec109685 10mo agoThe article info is great, but why do people put up with LLM ticks and slop in their writing? These sentences add no value and treats the reader as stupid. > This is concurrency, not language magic. > This is filesystem ops, not language-dependent. Duh, you literally told me that the previous sentence and 50 million other times.
- aurumque 10mo agoThis kind of writing goes deeper than LLM's, and reflects a decline in both reading ability, patience, and attention. Without passing judgement, there are just more people now who benefit from repetition and summarization embedded directly in the article. The reader isn't 'stupid', just burdened.
- twoodfin 10mo agoIndeed, I am coming around in the past few weeks to realization and acceptance that the LLM editorial voice is a benefit to an order of magnitude more hn readers than those (like us) for whom it is ice pick in the nostril stuff. Oh well, all I can do is flag.
- hallvard 10mo agoGreat post, but the blatant chatgpt-esque feel hits hard… Don’t get me wrong, I love astral! and the content, but…
- hallvard 10mo agoReading the other replies here makes it really obvious that this is some LLM’s writing. Maybe even all of it…
- skywhopper 10mo agoThis is great to read because it validates my impression that Python packaging has always been a tremendous overengineered mess. Glad to see someone finally realized you just need a simple standard metadata file per package.
- zahlman 10mo agoIt has been realized in the Python community for a very long time. But there have been years of debate over the contents and formatting, and years of trying to figure out how to convince authors and maintainers to do the necessary work on their end, and years of trying to make sure the ecosystem doesn't explode from trying to remove legacy support. There are still separate forms of metadata for source packages and pre-compiled distributions. This is necessary because of all the weird idiosyncratic conditional logic that might be necessary in the metadata for platform-specific dependencies. Some projects are reduced to figuring out the final metadata at build time, while building on the user's machine, because that's the only way to find out enough about the user's machine to make everything work. It really isn't as straightforward as you'd expect, largely because Python code commonly interfaces to compiled code in several different languages, and end users expect this to "just work", including on Windows where they don't have a compiler and might not know what that is. See https://pypackaging-native.github.io/ https://pypackaging-native.github.io/ for the general flavour of it.
- markkitti 10mo ago"overengineered" is not the term I would use to describe Python packaging. I would say it is "under-engineered". As in, "Why engineer a configuration file when you can just do it in code?". This tendency towards what initially seems like the "simple" solution pervades the Python ecosystem and often requires complex engineering to work around later.
- looneysquash 10mo agoI don't have any real disagreement with any of the details the author said. But still, I'm skeptical. If it is doable, the best way to prove it is to actually do it. If no one implements it, was it ever really doable? Even if there is no technical reason, perhaps there is a social one?
- stevemk14ebr 10mo agoWhat are you talking about, this all exists
- zahlman 10mo agoI guess you mean doing the things in Python that are supposedly doable from Python. Yeah, to a zeroth approximation that's my current main project (https://github.com/zahlman/paper https://github.com/zahlman/paper). Of course, I'm just some rando with apparently serious issues convincing myself to put in regular unpaid work on it, but I can see in broad strokes how everything is going to work. (I'm not sure I would have thought about, for example, hard-linking files when installing them from cache, without uv existing.)
- VerifiedReports 10mo agoSo... will uv make Python a viable cross-platform utility solution? I was going to learn Python for just that (file-conversion utilities and the like), but everybody was so down on the messy ecosystem that I never bothered.
- zahlman 10mo agoIt has been viable for a long time, and the kinds of projects you describe are likely well served by the standard library.
- oblio 10mo agoIt hasn't been viable and you'd know if you tried to deploy Python scripts to Windows users and maintain/update them over longer periods of time.
- pseudosavant 10mo agoI write all of my scripts in Python with PEP 723 metadata and run them with `uv run`. Works great on Windows and Linux for me.
- IshKebab 10mo agoYes, uv basically solves the terrible Python tooling situation. In my view that was by far the biggest issue with Python - a complete deal-breaker really. But uv solves it pretty well. The remaining big issues are a) performance, and b) the import system. uv doesn't do anything about those. Performance may not be an issue in some cases, and the import system is ... tolerable if you're writing "a python project". If you're writing some other project and considering using Python for its scripting system, e.g. to wrangle multiple build systems or whatever than the import mess is a bigger issue and I would thing long and hard before picking it over Deno.
- VerifiedReports 10mo agoThanks! I don't really think about importing stuff (which maybe I should), because I assume I'll have to write any specialized logic myself. So... your outlook is encouraging.
- pwdisswordfishy 10mo ago> Some of uv’s speed comes from Rust. But not as much as you’d think. Several key optimizations could be implemented in pip today: […] Python-free resolution Umm…
- andy99 10mo agoI remain baffled about these posts getting excited about uv’s speed. I’d like to see a real poll but I personally can’t imagine people listing speed as one of the their top ten concerns about python package managers. What are the common use cases where the delay due to package installation is at all material? Edit to add: I use python daily
- gordonhart 10mo ago`poetry install` on my dayjob’s monolith took about 2 minutes, `uv sync` takes a few seconds. Getting 2 minutes back on every CI job adds up to a lot of time saved
- pants2 10mo agoThe biggest benefit is in CI environments and Docker images and the like where all packages can get reinstalled on every run.
- toenail 10mo agoThe speed is nice, but I switched because uv supports "pip compile" from pip-tools, and it is better at resolving dependencies. Also pip-tools uses (used?) internal pip methods and breaks frequently because of that, uv doesn't.
- recov 10mo agoDocker builds are a big one, at least at my company. Any tool that reduces wait time is worth using, and uv is an amazing tool that removes that wait time. I take it you might not use python much as it solves almost every pain point, and is fast which feels rare.
- stavros 10mo agoI can run `uvx sometool` without fear because I know that it'll take a few seconds to create a venv, download all the dependencies, and run the tool. uv's speed has literally changed how I work with Python.
- quectophoton 10mo ago
- zahlman 10mo agoI've talked about this many times on HN this year but got beaten to the punch on blogging it seems. Curses. ... Okay, after a brief look, there's still lots of room for me to comment. In particular: > pip’s slowness isn’t a failure of implementation. For years, Python packaging required executing code to find out what a package needed. This is largely refuted by the fact that pip is still slow, even when installing from wheels (and getting PEP 600 metadata for them). Pip is actually still slow even when doing nothing. (And when you create a venv and allow pip to be bootstrapped in it, that bootstrap process takes in the high 90s percent of the total time used.)
- didibus 10mo agoThere's an interesting psychology at play here as well, if you are a programmer that chooses a "fast language" it's indicative of your priorities already, it's often not much the language, but that the programmer has decided to optimize for performance from the get go.
- bastawhiz 10mo ago> When a package says it requires python<4.0, uv ignores the upper bound and only checks the lower. This reduces resolver backtracking dramatically since upper bounds are almost always wrong. Packages declare python<4.0 because they haven’t tested on Python 4, not because they’ll actually break. The constraint is defensive, not predictive. This is kind of fascinating. I've never considered runtime upper bound requirements. I can think of compelling reasons for lower bounds (dropping version support) or exact runtime version requirements (each version works for exact, specific CPython versions). But now that I think about it, it seems like upper bounds solve a hypothetical problem that you'd never run into in practice. If PSF announced v4 and declared a set of specific changes, I think this would be reasonable. In the 2/3 era it was definitely reasonable (even necessary). Today though, it doesn't actually save you any trouble.
- wging 10mo agoI think the article is being careful not to say uv ignores _all_ upper bound checks, but specifically 4.0 upper bound checks. If a package says it requires python < 3.0, that's still super relevant, and I'd hope for uv to still notice and prevent you from trying to import code that won't work on python 3. Not sure what it actually does.
- breischl 10mo agoI read the article as saying it ignores all upper-bounds, and 4.0 is just an example. I could be wrong though - it seems ambiguous to me. But if we accept that it currently ignores any upper-bounds checks greater than v3, that's interesting. Does that imply that once Python 4 is available, uv will slow down due to needing to actually run those checks?
- VorpalWay 10mo agoAre there any plans to actually make a 4.0 ever? I remember hearing a few years ago that after the transition to 3.0, the core devs kind of didn't want to repeat that mess ever again. That said, even if it does happen, I highly doubt that is the main part of the speed up compared to pip.
- ofek 10mo ago> pip could implement parallel downloads, global caching, and metadata-only resolution tomorrow. It doesn’t, largely because backwards compatibility with fifteen years of edge cases takes precedence. pip is simply difficult to maintain. Backward compatibility concerns surely contribute to that but also there are other factors, like an older project having to satisfy the needs of modern times. For example, my employer (Datadog) allowed me and two other engineers to improve various aspects of Python packaging for nearly an entire quarter. One of the items was to satisfy a few long-standing pip feature requests. I discovered that the cross-platform resolution feature I considered most important is basically incompatible [1] with the current code base. Maintainers would have to decide which path they prefer. [1]: https://github.com/pypa/pip/issues/13111 https://github.com/pypa/pip/issues/13111
- zahlman 10mo ago> pip is simply difficult to maintain. Backward compatibility concerns surely contribute to that but also there are other factors, like an older project having to satisfy the needs of modern times. Backwards compatibility is the one thing that prevents the code in an older project from being replaced with a better approach in situ. It cannot be more difficult than a rewrite, except that rewrites (arguably including my project) may hold themselves free to skip hard legacy cases, at least initially (they might not be relevant by the time other code is ready). (I would be interested in hearing from you about UX designs for cross-platform resolution, though. Are you just imagining passing command-line flags that describe the desired target environment? What's the use case exactly — just making a .pylock file? It's hard to imagine cross-platform installation....)
- eviks 10mo ago> Every code path you don’t have is a code path you don’t wait for. No, every code path you don't execute is that. Like > No .egg support. How does that explain anything if the egg format is obsolete and not used? Similar with spec strictness fallback logic - it's only slow if the packages you're installing are malformed, otherwise the logic will not run and not slow you down. And in general, instead of a list of irrelevant and potentially relevant things would be great to understand some actual time savings per item (at least those that deliver the most speedup)! But otherwise great and seemingly comprehensive list!
- zahlman 10mo ago> No, every code path you don't execute is that. Even in compiled languages, binaries have to get loaded into memory. For Python it's much worse. On my machine: $ time python -c 'pass' real 0m0.019s user 0m0.013s sys 0m0.006s $ time pip --version > /dev/null real 0m0.202s user 0m0.182s sys 0m0.021s Almost all of that extra time is either the module import process or garbage collection at the end. Even with cached bytecode, the former requires finding and reading from literally hundreds of files, deserializing via `marshal.loads` and then running top-level code, which includes creating objects to represent the functions and classes. It used to be even worse than this; in recent versions, imports related to Requests are deferred to the first time that an HTTPS request is needed.
- eviks 10mo ago> binaries have to get loaded into memory. Unless memory mapped by the OS with no impact on runtime for unused parts? > imports related to Requests are deferred Exactly, so again have no impact?
- zahlman 10mo ago> Unless memory mapped by the OS with no impact on runtime for unused parts? Yeah, this is presumably why a no-op `uv` invocation on my system takes ~50 ms the first time and ~10 ms each other time. > Exactly, so again have no impact? Only if your invocation of pip manages to avoid an Internet request. Note: pip will make an Internet request if you try to install a package by symbolic name even if it already has the version it wants in cache, because its cache is an HTTP cache rather than a proper download cache. But even then, there will be hundreds of imports mainly related to Rich and its dependencies.
- efilife 10mo agothis shit is ChatGPT-written and I'm really tired of it. If I wanted to read chatgpt I would have asked it myself. Half of the article are nonsensical repeated buzzwords thrown in for absolutely no reason
- IshKebab 10mo agoMmm I don't buy it. Not many projects use setup.py now anyway and pip is still super slow. > Plenty of tools are written in Rust without being notably fast. This also hasn't been my experience. Most tools written in Rust are notably fast.
- zahlman 10mo ago> Not many projects use setup.py now anyway and pip is still super slow. Yes, but that's still largely not because of being written in Python. The architecture is really just that bad. Any run of pip that touches the network will end up importing more than 500 modules and a lot of that code will simply not be used. For example, one of the major dependencies is Rich, which includes things like a 3600-entry mapping of string names to emoji; Rich in turn depends on Pygments which normally includes a bunch of rules for syntax highlighting in dozens of programming languages (but this year they've finished trimming those parts of the vendored Pygments). Another thing is that pip's cache is an HTTP cache. It literally doesn't know how to access its own package download cache without hitting the network, and it does that access through wrappers that rely on cachecontrol and Requests.
- IshKebab 10mo ago> Any run of pip that touches the network will end up importing more than 500 modules and a lot of that code will simply not be used. That's a property of Python though. The fact that it isn't compiled (and that importing is very slow). > a 3600-entry mapping of string names to emoji Which can easily be zero-cost in Rust. > It literally doesn't know how to access its own package download cache without hitting the network This is the only example you've given that actually fits with your thesis.
- zahlman 10mo ago> That's a property of Python though. The fact that it isn't compiled (and that importing is very slow). Bytecode compilation is compilation. There are many things that could be used to improve import speed that I never even see discussed, let alone implemented. But most importantly, pip doesn't need to have all these modules imported. They already proved they could defer the Requests imports; but the actual network calls aren't that hard to do with the standard library anyway. (As nice as it would be to have Requests in the standard library, but I digress.) Most of the stuff it imports up-front from Rich will go entirely unused. > Which can easily be zero-cost in Rust. Which is irrelevant to the point. > This is the only example you've given that actually fits with your thesis. No. My thesis is that pip doesn't have to be the way it is in order to actually solve the problem of installing Python packages. Everything I mentioned is an example of a thing pip doesn't have to do in order to install packages, and slows it down unnecessarily.
- ggm 10mo agoSome of these speed ups looked viable to backport into pip including parallel download, delayed .pyc, ignore egg, version checks. Not that I'd bother since uv does venv so well. But, "it's not all rust runtime speed" implies pip could be faster too.
- robertclaus 10mo agoAt Plotly we did a decent amount of benchmarking to see how much the different defaults `uv` uses lead to its performance. This was necessary so we could advise our enterprise customers on the transition. We found you lost almost all of the speed gains if you configured uv behave as much like pip as you could. A trivial example is the precompile flag, which can easily be 50% of pips install time for a typical data science venv. https://plotly.com/blog/uv-python-package-manager-quirks/ https://plotly.com/blog/uv-python-package-manager-quirks/
- zahlman 10mo agoThe precompilation thing was brought up to the uv team several months ago IIRC. It doesn't make as much of a difference for uv as for pip, because when uv is told to pre-compile it can parallelize that process. This is easily done in Python (the standard library even provides rudimentary support, which Python's own Makefile uses); it just isn't in pip yet (I understand it will be soon).
- w10-1 10mo agoI like the implication that we can have an alternative to uv speed-wise, but I think reliability and understandability are more important in this context (so this comment is a bit off-topic). What I want from a package manager is that it just works. That's what I mostly like about uv. Many of the changes that made speed possible were to reduce the complexity and thus the likelihood of things not working. What I don't like about uv (or pip or many other package managers), is that the programmer isn't given a clear mental model of what's happening and thus how to fix the inevitable problems. Better (pubhub) error messages are good, but it's rare that they can provide specific fixes. So even if you get 99% speed, you end up with 1% perplexity and diagnostic black boxes. To me the time that matters most is time to fix problems that arise.
- zahlman 10mo ago> the programmer isn't given a clear mental model of what's happening and thus how to fix the inevitable problems. This is a priority for PAPER; it's built on a lower-level API so that programmers can work within a clear mental model, and I will be trying my best to communicate well in error messages.
- pkaodev 10mo agoAI slop
- rvz 10mo agoTLDR: Because Rust. This entire AI generated article with lots of text just to just say the obvious.
- zahlman 10mo agoThat conclusion is largely false, and is not what the article says.
- punnerud 10mo agoMy favorite speed up trick: “ HTTP range requests for metadata. Wheel files are zip archives, and zip archives put their file listing at the end. uv tries PEP 658 metadata first, falls back to HTTP range requests for the zip central directory, then full wheel download, then building from source. Each step is slower and riskier. The design makes the fast path cover 99% of cases. None of this requires Rust.”
- zahlman 10mo ago> None of this requires Rust. Indeed. As demonstrated by the fact that pip has been doing exactly the same for years. Part of the reason things are improving is that "tries PEP 658 metadata first" is more likely to succeed, and at some point build tools may have become more aware of how pip expects the zip to be organized (see https://packaging.python.org/en/latest/specifications/binary-distribution-format/#recommended-archiver-features https://packaging.python.org/en/latest/specifications/binary...), and way more projects ship wheels (because the manylinux standard has improved, and because pure-Python devs have become aware of things like https://pradyunsg.me/blog/2022/12/31/wheels-are-faster-pure-python/ https://pradyunsg.me/blog/2022/12/31/wheels-are-faster-pure-...).
- simonw 10mo agoThis post is excellent. I really like reading deep dives like this that take a complex system like uv and highlight the unique design decisions that make it work so well. I also appreciate how much credit this gives the many previous years of Python standards processes that enabled it. Update: I blogged more about it here, including Python recreations of the HTTP range header trick it uses and the version comparison via u64 integers: https://simonwillison.net/2025/Dec/26/how-uv-got-so-fast/ https://simonwillison.net/2025/Dec/26/how-uv-got-so-fast/
- BiteCode_dev 10mo agoOther design decisions that made uv fast: - uncompressing packages while they are still being downloaded, in memory, so that you only have to write to disk once - design of its own locking format for speed But yes, rust is actually making it faster because: - real threads, no need for multi-processing - no python VM startup overhead - the dep resolution algo is exactly the type of workload that is faster in a compiled language Source, this interview with Charlie Marsh: https://www.bitecode.dev/p/charlie-marsh-on-astral-uv-and-the https://www.bitecode.dev/p/charlie-marsh-on-astral-uv-and-th... The guy has a lot of interesting things to say.
- zzzeek 10mo ago> real threads, no need for multi-processing parallel downloads don't need multi-processing since this is an IO bound usecase. asyncio or GIL-threads (which unblock on IO) would be perfectly fine. native threads will eventually be the default also.
- BiteCode_dev 10mo agoIndeed, but unzipping while downloading do. Analysing multiple metadata files and exporting lock data as well. Now I believe unzip releases the GIL already so we could already benefit from that and the rest likely don't dominate perfs. But still, rust software is faster on average than python software. After all, all those things are possible in python, and yet we haven't seen them all in one package manager before uv. Maybe the strongest advantage of rust, on top of very clean and fast default behaviors, is that it attracts people that care about speed, safety and correctness. And those devs are more likely to spend time implementing fast software. Thought the main benefit of uv is not that it's fast. It's very nice, and opens more use cases, but it's not the killer feature. The killer feature is, being a stand alone executable, it bypasses all python bootstrapping problems. Again, that could technically be achieved in python, but friction is a strong force.
- zzzeek 10mo ago> Maybe the strongest advantage of rust, on top of very clean and fast default behaviors, is that it attracts people that care about speed, safety and correctness. And those devs are more likely to spend time implementing fast software. people who have this opinion should use Rust, not Python, at all. if Python code does not have sufficient speed, safety, and correctness for someone, it should not be used. Python's tools should be written in Python. > The killer feature is, being a stand alone executable, it bypasses all python bootstrapping problems. I can't speak for windows or macs but on Linux, system pythons are standard, and there is no "bootstrapping problem" using well known utilities that happen to be written in Python.
- hk1337 10mo agoIt’s fast because it sucks the life force from bad developers to make them into something good. Jokes aside… I really like uv but also really like mise and I cannot seem to get them to work well together.
- Onavo 10mo agoWhy? They are pretty compatible. Just set the venv in the project's mise.toml are you are good to go. Mise will activate it automatically when you change into the project directory.
- hk1337 10mo agoI believe I was trying it the other way around. I installed uv and python with mise but uv still created a .python_version file and using the one installed in the system instead of what was in mise
- didip 10mo agoIf UV team has a spare time, they should rewrite Python in Rust without any of the legacy baggage.
- zzzeek 10mo ago> pip could implement parallel downloads, global caching, and metadata-only resolution tomorrow. It doesn’t, largely because backwards compatibility with fifteen years of edge cases takes precedence. But it means pip will always be slower than a tool that starts fresh with modern assumptions. what does backwards compatibility have to do with parallel downloads? or global caching? The metadata-only resolution is the only backwards compatible issue in there and pip can run without a setup.py file being present if pyproject.toml is there. Short answer is most, or at least a whole lot, of the improvements in uv could be integrated into pip as well (especially parallelizing downloads). But they're not, because there is uv instead, which is also maintained by a for-profit startup. so pip is the loser
- orliesaurus 10mo agoThe most surprising part of uv's success to me isn't Rust at all, it's how much speed we "unlocked" just by finally treating Python packaging as a well-specified systems problem instead of a pile of historical accidents. If uv had been written in Go or even highly optimized CPython, but with the same design decisions (PEP 517/518/621/658 focus, HTTP range tricks, aggressive wheel-first strategy, ignoring obviously defensive upper bounds, etc.), I strongly suspect we'd be debating a 1.3× vs 1.5× speedup instead of a 10× headline — but the conversation here keeps collapsing back to "Rust rewrite good/bad." That feels like cargo-culting the toolchain instead of asking the uncomfortable question: why did it take a greenfield project to give Python the package manager behavior people clearly wanted for the last decade?
- edoceo 10mo agoConsensus building and figuring out what was actually needed? Someone on this site said most tech problems are people problems - this feels like one. Greenfield mostly solves the problem because it's all new people.
- morshu9001 10mo agoI can't find the quote for this, but I remember Python maintainers wanted package installing and management to be separate things. uv did the opposite, and instead it's more like npm.
- MBCook 10mo agoDo you remember the reason? I spend most of my time in the Java and JS ecosystems where one tool does both jobs. In my mind they’re pretty heavily linked. But that may be based on not experiencing the opposite. At least not as far as I can remember.
- morshu9001 10mo agoSeparation of concerns. They just wanted pip to be a good installer and let package managers* use pip. But uv didn't end up doing that either. * or workflow tools as they're called here https://packaging.python.org/en/latest/guides/tool-recommendations/#workflow-tools https://packaging.python.org/en/latest/guides/tool-recommend...
- dangoodmanUT 10mo ago> Zero-copy deserialization Just a nit on this section: zero-copy deserialization is not Rust specific (see flatbuffers). rkyv as a crate for doing so in Rust is though
- est 10mo ago> Virtual environments required This bothers me more than once when building a base docker image. Why would I want a venv inside a docker with root?
- forrestthewoods 10mo agoBecause a single docker image can run multiple programs that have mutually exclusive dependencies? Personally I never want program to ever touch global shared libraries ever. Yuck.
- est 10mo ago> a single docker image can run multiple programs You absolutely can. But it's not best practice. https://docs.docker.com/engine/containers/multi-service_container/ https://docs.docker.com/engine/containers/multi-service_cont...
- forrestthewoods 10mo agoGod I hate docker so much. Running computers does not have to be so bloody complicated.
- pornel 10mo agoThe old package managers messing up the global state by default is the reason why Docker exists. It's the venv for C.
- shevy-java 10mo agoSoon uv will deliver results without you even thinking about them beforehand!
- deleted 10mo ago[deleted]
- PrettyPastry 10mo agoI wish this were enough to get the flake8 devs to accept pyproject support PRs.
- dxdm 10mo agoStop using flake8 and use ruff instead. It's made by the same folks that make uv.
- dmarwicke 10mo agowait, zero-copy deserialization isn't rust-specific. you can mmap structs in C. done it before, works fine
- zahlman 10mo agoThe point is that it would be difficult in Python, compared to in "system" compiled languages generally.
- rao-v 10mo agoI have to say it's just lovely seeing such a nicely crafted and written technical essay. It's so obvious that this is crafted by hand, and reading it just reemphasises how much we've lost because technical bloggers are too ready to hand the keys over to LLMs.
- yakshaving_jgt 10mo agoThis post was very clearly written with an LLM.
- man4 10mo ago[dead]
- annexrichmond 10mo ago> This reduces resolver backtracking dramatically since upper bounds are almost always wrong. I am surprised by this because Python minor versions break backwards compatibility all the time. Our company for example is doing a painful upgrade from py39 to py311
- zahlman 10mo agoCould you explain what major pain points you've encountered? I can't think of any common breakages cited in 3.10 or 3.11 offhand. 3.12 had a lot more standard library removals, and the `match` statement introduced in 3.10 uses a soft keyword and won't break code that uses `match` as an identifier.
- pjjpo 10mo ago> npm’s package.json is declarative lol
- Revisional_Sin 10mo ago> Ignoring requires-python upper bounds. When a package says it requires python<4.0, uv ignores the upper bound and only checks the lower. This reduces resolver backtracking dramatically since upper bounds are almost always wrong. Packages declare python<4.0 because they haven’t tested on Python 4, not because they’ll actually break. The constraint is defensive, not predictive. Erm, isn't this a bit bad?
- Pawamoy 10mo agoNo. When such upper bounds are respected, they contaminate other packages, because you have to add them yourself to be compatible with your dependencies. Then your dependents must add them too, etc. This brings only pain. Python 4 is not even a thing, core developers say there won't ever be a Python 4.h
- akoboldfrying 10mo ago> you have to add them yourself to be compatible with your dependencies This is no more true for version upper bounds than it is for version lower bounds, assuming that package installers ensure all package version constraints are satisfied. I presume you think version lower bounds should still be honoured?
- zahlman 10mo agoThe point is that you can know that a lower bound is necessary at the time of publication; an upper bound is either speculative or purely defensive, and has possibly unnecessary consequences for your dependents.
- akoboldfrying 10mo agoYou can also know that an upper bound is necessary at the time of publication -- for example, if your foo project uses bar 2.0, and bar 3.0 has already come out, and you have tried it and found it incompatible. In the reverse direction, many version lower bounds are also "purely defensive" -- arising from nothing more than the version of the dep that you happened to get when you started the project. (Just because you installed "the latest baz" and got version 2.3.4, without testing there is nothing to say that version 2.3.3 would also work fine, so adding the version lower bound >=2.3.4 is purely defensive). Basically, the two bound types are isomorphic.
- almosthere 10mo agoOur next trick, getting people to stop writing code (so we can stop writing python)
- TrayKnots 10mo agoI usually don't see the importance of speed in one-time costs... But hey, same discussion with npm, yarn, pnpm...
- vjay15 10mo agoAmazing that how much python's pip was so bottlenecked, it was basic design problem damn
- sghaz 10mo agoLiked the focus on standards and ecosystem decisions rather than just “it’s fast because Rust.” One small timeline nit: the article mentions PEP 517 as being from 2017, but the PEP itself was created in 2015. From the PEP header: Created: 30-Sep-2015 [1] It did see important revisions and wider adoption around 2017, so I assume that’s what was meant. [1] https://peps.python.org/pep-0517/ https://peps.python.org/pep-0517/
- pritambarhate 10mo agoDoes anyone know how does Astral.sh plan to make money?
- zahlman 10mo agoVia services like https://astral.sh/pyx https://astral.sh/pyx , IIRC.
- trashburger 10mo ago> Ignoring requires-python upper bounds. When a package says it requires python<4.0, uv ignores the upper bound and only checks the lower. This reduces resolver backtracking dramatically since upper bounds are almost always wrong. Packages declare python<4.0 because they haven’t tested on Python 4, not because they’ll actually break. The constraint is defensive, not predictive. This is clearly LLM-generated and the other bullet points have the same smell. Please use your own words.
- quantbagel 10mo agoWhen I made a swift package manager as Rust rewrite I realized that the language wasn't the issue, design is a lot more important. Rust just gave a boost to everything else. you can try Gust here https://github.com/quantbagel/gust https://github.com/quantbagel/gust a lot better than using SwiftPM but there's room for improvement! Make issues with your ideas
- ldng 10mo agoThat might be how uv got fast but that is not why it got popular. PyPA has been a mess for a very long time for in-fighting, astroturfing, gatekeeping and so on with pip being the battlefield. The uv team just did one thing that PyPA & co stopped doing a long time ago (if they ever did ...) : actually solving pain point of their user and never saying "it's not possible because [insert bullshit]" or reply "it's OSS, do it yourself" to then reject the work with attitude and baseless argument. They listened to their user's issues and solved their pain points without denying them. period.
- markkitti 10mo agoSummary: They fixed Python packaging by not having to run Python to resolve dependencies. Also, they used Rust. Moral of the story: Use less Python. Use declarative configuration and other langauges instead.
- srikanthdotch 10mo agoI am glad uv exists, I came back to using python regularly because of that. The company behind it "astral" is interesting and they seem to care about developer experience.
- Woodi 10mo agoLOL All that years (decades?) Python had build-in code executed on install ??? That's functionally adequate to MS autoexec.bat viruses spreading mechanism! Just LOL...
- Unissued2003 10mo agoI think one thing that gets lost in the uv noise is that recent versions of pip are significantly faster. It would be much better to just make pip faster and add features to it instead of introducing a completely new package manager. Hopefully uv will do what yarn did to JavaScript - turbocharge some improvements in the core tooling through competitive pressure.
- ianberdin 10mo agoIsn’t it obvious?