Live data from Hacker News

Performance hacks for faster Python code

blog.jetbrains.com

41–50 of 65 posts

Re: Performance hacks for faster Python code

#41
post #37

Earlier quoted context omitted.

Python's strong native story has always been one of its biggest draws: people find it ironic that so much of the Python ecosystem is native code, but it plays to Python's strength (native code where performance matters, Python for developer joy/ergonomics/velocity). > Even if you call into fast code from Python you still have to contend with the GIL which I find very limiting for anything resembling performance. It d…

I didn't know about detaching from the GIL... I'll look into that. > native code where performance matters, Python for developer joy/ergonomics/velocity Makes sense, but I guess I just feel like you can eat your cake and have it too by using another language. Maybe in the past there was a serious argument to be made about the productivity benefits of Python, but I feel like that is becoming less and less the case. Pe…

Yes, I think Python is excellent evidence that developer ecosystems (libraries, etc.) are paramount. Developer ergonomics are important, but I think one of the most interesting lessons from the last decade is that popular languages/ecosystems will converge onto desirable ergonomics.

Re: Performance hacks for faster Python code

#42
post #29
post #18

Earlier quoted context omitted.

It's kinda funny how uv is written in Rust and many Python libraries where performance is expected to matter (NumPy, Pandas, PyTorch, re, etc.) are implemented in C. Even if you call into fast code from Python you still have to contend with the GIL which I find very limiting for anything resembling performance.

In my analysis, the lion's share of uv's performance improvement over pip is not due to being written in Rust. Pip just has horrible internal architecture that can't be readily fixed because of all the legacy cruft. And for numerical stuff it's absolutely possible to completely trash performance by naively assuming that C/Rust/Fortran etc. will magically improve everything. I saw an example in a talk once where it su…

> Pip just has horrible internal architecture that can't be readily fixed because of all the legacy cruft.

Interesting... I didn't know that. So they should be able to get similar results in Python then?

> absolutely possible to completely trash performance by naively assuming

Yeah, of course we'd need a specific benchmark to compare results. It totally depends on the problem that you're trying to solve.

Re: Performance hacks for faster Python code

#43
post #23

I'm sure this is plenty useful for less experienced people, but the "smart" hacks read a bit like: Hack 1: Don't Use The Obviously Wrong Data Structure For Your Problem! Hack 2: Don't Have The Computer Do Useless Stuff! Hack 3: Don't Allocate Memory When You Don't Need To! And now, a word from our sponsor: AI! Use AI to help AI build AI with AI, now with 15% more AI! Only with AI! Ask your doctor if AI is right for y…

Not to mention that a lot of these performance improvements, while sane, are on the order of milliseconds of improvement. Unless you're doing one of these unoptimized approaches thousands or millions of times in a tight loop you're probably not saving a substantial amount of time/energy/computation. Premature optimization is still the root of all evil!

If you want an actual performance improvement in Python code that most people wouldn't necessarily expect: consider using regexes for even basic string parsing if you're doing a lot of it, rather than doing it yourself (e.g. splitting strings, then splitting those strings, etc.); while regexes "feel" like they should be more complicated and therefore slower or less efficient, the regex engine in Python is implemented in C and there's a decent chance that, with a little tweaking, even simple string processing can be done faster with a regex. Again only important in a hot loop, but still.

Re: Performance hacks for faster Python code

#44
post #42
post #29

Earlier quoted context omitted.

In my analysis, the lion's share of uv's performance improvement over pip is not due to being written in Rust. Pip just has horrible internal architecture that can't be readily fixed because of all the legacy cruft. And for numerical stuff it's absolutely possible to completely trash performance by naively assuming that C/Rust/Fortran etc. will magically improve everything. I saw an example in a talk once where it su…

> Pip just has horrible internal architecture that can't be readily fixed because of all the legacy cruft. Interesting... I didn't know that. So they should be able to get similar results in Python then? > absolutely possible to completely trash performance by naively assuming Yeah, of course we'd need a specific benchmark to compare results. It totally depends on the problem that you're trying to solve.

> So they should be able to get similar results in Python then?

I'm making PAPER (https://github.com/zahlman/paper) which is intended to prove as much, while also filling some under-served niches (and ignoring or at least postponing some legacy features to stay small and simple). Although I procrastinated on it for a while and have recently been distracted with factoring out a dependency... I don't want to give too much detail until I have a reasonable Show HN ready.

But yeah, a big deal with uv is the caching it does. It can look up wheels by name and find already-unpacked data, which it hard-links into the target environment. Pip unpacks from the wheel each time (which also entails copying the data rather than doing fast filesystem operations, and its cache is an HTTP cache, which just intercepts the attempt to contact PyPI (or whatever other specified index).

Python offers access to hard links (on systems that support them) in the standard library. All the filesystem-related stuff is already implemented in C under the hood, and a lot of the remaining slowness of I/O is due to unavoidable system calls.

Another big deal is that when uv is asked to precompile .pyc files for the installation, it uses multiple cores. The standard library also has support for this (and, of course, all of the creation of .pyc files in CPython is done at the C level); it's somewhat naive, but can still get most of the benefit. Plus, for the most part the precompiled files are also eligible for caching, and last time I checked even uv didn't do that. (I would not be at all surprised to hear that it does now!)

> It totally depends on the problem that you're trying to solve.

My point was more that even when you have a reasonable problem, you have to be careful about how you interface to the compiled code. It's better to avoid "crossing the boundary" any more than absolutely necessary, which often means designing an API explicitly around batch requests. And even then your users will mess it up. See: explicit iteration over Numpy/Pandas data in a Python loop, iterative `putpixel` with PIL, any number of bad ways to use OpenGL bindings....

Re: Performance hacks for faster Python code

#45
post #25

Earlier quoted context omitted.

> what can you do besides copy, modify, and return a new object? You can directly produce a modified copy, rather than using a mutating operation to implement the modifications. It should be noted that "return a modified copy" algorithms can be much more efficient than "mutate the existing data" ones. For example, consider the case of removing multiple elements from a list, specified by a predicate. The version of th…

swap with last element then truncate at the end

Yes, you can do this if you don't care about order, and avoid the performance degradation. But it's even more complex.

Or if you do care about order, you can emulate the C++ "erase-remove" idiom, by keeping track of separate "read" and "write" positions in the source, iterating until "read" reaches the end, and only incrementing "write" for elements that are kept; and then doing a single `del` of a slice at the end. But this, too, is complex to write, and very much the sort of thing one chooses Python in order to avoid. And you do all that work, in essence, just to emulate what the list comprehension does but in-place.

Re: Performance hacks for faster Python code

#46
post #35

Earlier quoted context omitted.

Math is a lost cause, yeah, just use numpy (with the important caveat that you need to know what you're doing, it's easy to fumble badly). But Python has a few interesting features that can easily get you big wins, like generators, e.g. https://www.dabeaz.com/generators/Generators.pdf

A lot of interesting math can't be done in numpy, sadly. At that point you might be better off writing the initial version in Python and translating it to something else. A friend of mine asked me to translate some (well-written) number theory code a while back, I got about a 250x speedup just doing a line by line translation from Python to Julia. But the problem was embarrassingly parallel, so I was able to slap on…

I 'wrote' (adapted from the Rich project's example code) a simple concurrent file downloader in Python; run 'download ' and it goes and downloads each one, assuming that the URL has what looks like a filename at the end or the server response with a Content-Disposition header that contains a filename. It was very simple; spawn a thread for each file we're downloading, show a progress bar for each file we're downloading, update the progress bar as we download.

I ended up rewriting the whole thing in Rust (my first Rust project) solely because I noticed that just that simple process - "get some bytes from the network, write them to this file descriptor, update the progress bar's value" was churning my CPU due to how intensive it was for the progress bar to update as often as it was - which wasn't often.

Because of how ridiculous it was I opted to rewrite it in another language; I considered golang but all of the progress bar libraries in Golang are mediocre at best, and I liked the idea of learning more Rust. Surprise surprise, it's faster and more efficient; it even downloads faster, which is kind of ridiculous.

An even crazier example: a coworker was once trying to parse some giant logfile and we ended up nerd-sniping ourselves into finding ways to speed it up (even though it finished while we were doing so). After profiling this very simple code, we found that 99% of the time in processing each line was simply parsing the date, and 99% of that was because Python's strptime is devoted to being able to parse timezones even if the input you're giving it doesn't include one. We played around with things like storing a hash map of "string date to python datetime" since there were a lot of duplicates, but the fastest method was to write an awful Python extension that basically just exposed glibc's strptime so you could bypass Python's (understandably) complex tz parsing. For the version of Python we were using it made parsing hundreds of thousands of dates 47x faster, though now in Python3 it's only about 17x faster? Maybe less.

https://github.com/danudey/pystrptime

I still use Python all the time because usually the time I save writing my code quickly more than outweighs the time I spend having slower code overall; still, if your code is going to live a while, maybe try running it through a profiler and see what surprises you can find.

Re: Performance hacks for faster Python code

#47
post #12

What about using PyPy? You'll probably see a significant improvement in these benchmarks. You should also give it a shot in Node which I expect to be about on par with PyPy, but without the GIL.

If anyone wants to be surprised by optimization, a great way to do it is to look at all the cases where, even though Python is slower than C, the Python interpreter written in Python is faster than the Python interpreter written in C.

Also, if we're going to suggest 'write it in another language' approaches, rewrite it in Golang. I detest writing in Golang but once you get the hang of things you can get to the point where your code only takes twice the time to write and 2% of the time (and memory) to run.

Re: Performance hacks for faster Python code

#48
post #44
post #42

Earlier quoted context omitted.

> Pip just has horrible internal architecture that can't be readily fixed because of all the legacy cruft. Interesting... I didn't know that. So they should be able to get similar results in Python then? > absolutely possible to completely trash performance by naively assuming Yeah, of course we'd need a specific benchmark to compare results. It totally depends on the problem that you're trying to solve.

> So they should be able to get similar results in Python then? I'm making PAPER ( https://github.com/zahlman/paper ) which is intended to prove as much, while also filling some under-served niches (and ignoring or at least postponing some legacy features to stay small and simple). Although I procrastinated on it for a while and have recently been distracted with factoring out a dependency... I don't want to give too…

> explicit iteration over Numpy/Pandas data in a Python loop

Yeah, I get it. I see the same thing pretty often... The loop itself is slow in Python so you have APIs that do batch processing all in C. Eventually I think to myself, "All this glue code is really slowing down my C." haha

Re: Performance hacks for faster Python code

#49
post #23

I'm sure this is plenty useful for less experienced people, but the "smart" hacks read a bit like: Hack 1: Don't Use The Obviously Wrong Data Structure For Your Problem! Hack 2: Don't Have The Computer Do Useless Stuff! Hack 3: Don't Allocate Memory When You Don't Need To! And now, a word from our sponsor: AI! Use AI to help AI build AI with AI, now with 15% more AI! Only with AI! Ask your doctor if AI is right for y…

“Hacks” 4-10 could easily be replaced with “use numpy.” Performance gains from doing math better in pure Python are minimal compared with numpy. It’s not unusual for the numpy version of something to end up taking 0.01x as long to run.

Use polars vs pandas. This alone saves me more time than any other “hack”.

Re: Performance hacks for faster Python code

#50
post #47
post #12

What about using PyPy? You'll probably see a significant improvement in these benchmarks. You should also give it a shot in Node which I expect to be about on par with PyPy, but without the GIL.

If anyone wants to be surprised by optimization, a great way to do it is to look at all the cases where, even though Python is slower than C, the Python interpreter written in Python is faster than the Python interpreter written in C. Also, if we're going to suggest 'write it in another language' approaches, rewrite it in Golang. I detest writing in Golang but once you get the hang of things you can get to the point…

> rewrite it in Golang

Totally, I'm a big fan of statically typed, compiled languages; especially when the codebase is large and/or there are a lot of contributors. I chose the Node example because I feel like it offers the same "ease-of-use" that draws people to Python.

> get to the point where your code only takes twice the time to write and 2% of the time (and memory) to run.

100%. Sometimes this matters, sometimes it doesn't, but if we're talking about "smart performance hacks" this is definitely a top contender.

I work on a Python project and I really wish that it supported multi-threading. If I rewrote it, I would prioritize that feature in the target language.

Post reply on HN