19 ms·
Modern Python Performance Considerations
- deleted 4y ago[deleted]
- SuaveSteve 4y agoWhy not switch to making __slots__ in classes the default and then making attribute changes to an object during runtime an opt-in? It will require a long grace period but wouldn't it help optimisation efforts immensely?
- yedpodtrzitko 4y agoThat would mean all installed dependencies need to comply with this change as well, which is unlikely to happen in any realistic timeframe.
- gjulianm 4y agoThat's going to require quite a lot of changes, it's a giant breaking change. All classes would need someone to go around finding all the attributes that are created and adding an __slots__ dictionary, to avoid regular attribute initialization in __init__ failing. It's a massive task, and it would completely break backwards compatibility for performance gains that not everybody will need.
- BurningFrog 4y agoWhere can I read about what kind of performance improvements `__slots__` brings?
- WillDaSilva 4y agoThe Python docs themselves is a good place to start: https://docs.python.org/3/reference/datamodel.html#slots https://docs.python.org/3/reference/datamodel.html#slots The Python wiki also has some good info about it: https://wiki.python.org/moin/UsingSlots https://wiki.python.org/moin/UsingSlots
- disconnect3d 4y agoApart from the official docs, this video also explains the low level data layout (in CPython) that slots introduce: https://www.youtube.com/watch?v=Iwf17zsDAnY https://www.youtube.com/watch?v=Iwf17zsDAnY
- anamax 4y agodefault __slots__ breaks a lot of monkey patching. An "easier" change would be to add a class attribute "no__dict__", which says that the __dict__ attribute can't be used, which lets the implementation do whatever it wants. That can be incrementally added to classes. Another option is a "no__getattr__" attribute, which disables gettattr and friends.
- s_Hogg 4y agoGreat read, vaguely reminds me someone or other was trying to get cpython going with cosmopolitan libc. Wonder what that would do for speed.
- make3 4y agowhy do you think that this would help performance? a quick read says cosmo is slower than regular libc. maybe it would be more portable, but not faster
- jerf 4y ago"Those techniques are based on the idea that most code "does not use the full dynamic power that it could at any given time" and that Python can quickly check to see if they are using the dynamic features." If anyone has a burning desire to try to write the next big dynamically-typed scripting language, I've often noodled in my head with the idea of a language that has a dynamically-typed startup phase, but at some point you call "DoneBeingDynamic()" on something (program, module, whatever, some playing would have to be done here) and the dynamic system basically freezes everything into place and becomes a static system. (Or you have an explicit startup phase to your module, or something like that.) The core observation I'm driving this on is much the same as the quote I give from the article. You generally set up the vast majority of your "dynamicness" once at runtime, e.g., you set up your monkeypatches, you read the tables out of the DB to set up your active classes, you read the config files and munge together the configurations, etc. But then forever after, your dynamic language is constantly reading this stuff, over and over and over and over again, millions, billions, trillions of times, with it never changing. But it has to be read for the language to work. Combine that with perhaps some work on a system that backs to a struct-like representation of things rather than a hash-like representation, and you might be able to build something that gets, say, 80% of the dynamicness of a 1990s-era dynamic scripting language, while performing at something more like compiled language speeds, albeit with a startup cost. If you could skip over the dozens of operations resolving x.y.z.q = 25 a dynamically-typed language like Python needs to properly implement that and get down to a runtime that can do the same thing compiled languages do by pre-computing the offset into a struct and just setting the value, you might get near static-language performance with dynamic typing affordances. You can also view this as a Lisp-like thing that has an integrated phase where it has macros, but then at some point puts this capability down. I tend to think it's just fundamentally flawed to take a language that is intrinsically defined as "x.y.z.q" requiring dozens of runtime operations versus trying to define a new one where it is a first-class priority from day one that the system be able to resolve that down to some static understanding of what "x.y.z.q" is. e.g., it's OK if y is a property and z is some fancy override if the runtime can simply hardcode the relevant details instead of having to resolve them every time. You can outrun even JIT-like optimizations if you can get this down to the point where you don't even have to check incoming types, you just know.
- 4y ago
- dijit 4y agoAs a very partial; almost unrelated question: Is there any python module that you use day-to-day that you'd like to have a significant speedup with? I'm thinking of reimplementing some python modules in rust, as that seems like the kind of weird thing I'm in to. I've done it with some success (using the excellent work of the pyo3 project) professionally, but I'd be interested in doing more.
- mritchie712 4y agopandas
- mynameis_jeff 4y agowould give https://github.com/modin-project/modin https://github.com/modin-project/modin a shot
- SnooSux 4y agoIt's been done: https://github.com/pola-rs/polars https://github.com/pola-rs/polars But I'm sure there's always room for improvement
- rytill 4y agoIt’s not like polars is a drop-in replacement, it has a totally different API.
- mrtranscendence 4y agoYou wrote "it has a totally different API", did you mean "it has an actually sane API?" Because that's what I think of when I compare pandas to polars.
- ies7 4y agoNot if you've legacy apps using pandas everywhere. Same API means I can import polars as pd and be done with it.
- dataflow 4y ago> Python can quickly check to see if they are using the dynamic features I don't understand how this is supposed to be "quickly" verifiable? Nothing prevents you from doing eval('gl' + 'obals')()['len'] = ...; how is the interpreter supposed to quickly check that this isn't the case when you're calling a function that might not even be in the current module? Doing this correctly would seem to require a ton of static analysis on the source or bytecode that I imagine will at best be slow, and at worst impossible due to the halting problem.
- gpderetta 4y agoOne possibility is to move the cost to the assignment, so the code that assigns a new value to the global 'len' function is going to track and invalidate all cached lookups. Hopefully you are changing the binding of 'len' less often than you are calling it :)
- deleted 4y ago[deleted]
- bootwoot 4y agoI was reading this as an undetailed description of state available WITHIN the interpreter. Probably there is a table of globals that you can simply check last modification on or something like this. Whether you hit it with eval or some other tricky code, you can't modify a global without the interpreter knowing about it.
- 4y ago
- kzrdude 4y agoFaster-cpython is not the main topic here but certainly welcome since it's the most used python. They've done great things so far. Though I remember I heard the promise of 50% improvement in each of five separate steps :)
- uncomputation 4y agoWith JavaScript, these kinds of optimizations in an engine make sense due to the web being limited by it and thus speed is a huge factor. With Python, however, if a Python web framework is “too” slow, I would honestly say the problem is using Python at all for a web server. Python shines beautifully as a (somewhat) cross platform scripting language: file reading and writing, environment variables, simple implementations of basic utilities: sort, length, max, etc that would be cumbersome in C. The move of Python out of this and into practically everything is the issue and then we get led into rabbit holes such as this where since we are using Python, a dynamic scripting language, for things a second year computer science student should know are not “the right jobs for the tool.” Instead of performance, I’d like to see more effort in portability, package management, and stability for Python because, essentially since it is often enterprise managed, juggling fifteen versions of Python where 3.8.x supports native collection typing annotations but we use 3.7.x, etc. is my biggest complaint. Also up there is pip and just the general mess of dependencies and lack of a lock file. Performance doesn’t even make the list. This is not to discredit anyone’s work. There is a lot of excellent technical work and research done as discussed in the article. I just think honestly a lot of this effort is wasted on things low on the priority tree of Python.
- rmbyrro 4y agoTotally agree that performance is not on my top 10 wish list for Python. But I disagree on "not the right jobs for the tool". Python is extremely versatile and can be used as a valid tool for a lot of different jobs, as long as it fits the job requirements, performance included. It doesn't require a CS degree to know that fitting job requirements and other factors like the team expertise, speed, budget, etc, are more important than fitting a theoretical sense of "right jobs for the tool".
- blagie 4y ago> It doesn't require a CS degree to know that fitting job requirements and other factors like the team expertise, speed, budget, etc, are more important than fitting a theoretical sense of "right jobs for the tool". It requires experience. A lot of those lessons only come after you've seen how much more expensive it is to maintain a system than to develop one, and how much harder people issues are than technical issues. A CS degree, or even a junior developer, won't have that.
- joncatanio 4y agoThis is a great read, and it's fantastic to see all the work being done to evaluate and improve the language! The dynamic-nature of the language is actually something that I had studied a few years back [1]. Particularly the variable and object attribute look ups! My work was just a master's thesis, so we didn't go too deep into more tricky dynamic aspects of the language (e.g. eval, which we restricted entirely). But we did see performance improvements by restricting the language in certain ways that aid in static analysis, which allowed for more performant runtime code. But for those interested, the abstract of my thesis [2] gives more insight into what we were evaluating. Our results showed that restricting dynamic code (code that is constructed at run time from other source code) and dynamic objects (mutation of the structure of classes and objects at run time) significantly improved the performance of our benchmarks. There was also some great discussion on HN when I had posted our findings as well [3]. [1]: https://github.com/joncatanio/cannoli https://github.com/joncatanio/cannoli [2]: https://digitalcommons.calpoly.edu/theses/1886/ https://digitalcommons.calpoly.edu/theses/1886/ [3]: https://news.ycombinator.com/item?id=17093051 https://news.ycombinator.com/item?id=17093051
- Animats 4y agoBut we did see performance improvements by restricting the language in certain ways that aid in static analysis, which allowed for more performant runtime code. Well, yes. In Python, one thread can monkey-patch the code in another thread while running. That feature is seldom used. In CPython, the data structures are optimized for that. Underneath, everything is a dict. This kills most potential optimizations, or even hard-code generation. It's possible to deal with that efficiently. PyPy has a compiler, an interpreter, and something called the "backup interpreter", which apparently kicks in when the program being run starts doing weird stuff that requires doing everything in dynamic mode. I proposed adding "freezing", immutable creation, to Python in 2010, as a way to make threads work without a global lock.[1] Guido didn't like it. Threads in Python still don't do much for performance. [1] http://www.animats.com/papers/languages/pythonconcurrency.html http://www.animats.com/papers/languages/pythonconcurrency.ht...
- chrisseaton 4y ago
- DeathArrow 4y ago>The first topic he raised, "why Python is slow", is somewhat divisive What dynamic, interpreted, single threaded language is fast?
- Qem 4y agoLua (LuaJIT implementation). Some Smalltalk VMs are also quite fast. For example, see Eliot Miranda work on CogVM.
- astrobe_ 4y agoYour pushing it a bit too far if you say that JIT is interpreted. To answer OP, if you replace "dynamic" by "untyped", Forth qualifies. And it actually can go where there's no JIT to save your A from the "just throw more hardware (and software) at the problem" mindset.
- Qem 4y agoI think someone once said dynamic langs must cheat to be performant. Jitted runtimes are just interpreters cheating.
- brokencode 4y agoAs far as interpreted languages go, Wren is pretty quick, but still not fast compared to compiled languages. But for dynamic, single threaded languages, JavaScript is famously fast with a modern JIT compiler like V8.
- baisq 4y agoPractically every other language that ticks those boxes is faster than Python.
- bsder 4y ago> What dynamic, interpreted, single threaded language is fast? Javascript. End of list. The problem is that a Javascript implementation is now so complicated that you can't develop a new one without massive investment of resources.
- DeathArrow 4y agoAs I see it, Python is good for glue code and small scripts where performance usually doesn't matter. Even if it would be more performant, it would be a nightmare for large code bases since it's dynamically typed. I really enjoy Nim which is "slick as Python, fast as C".
- supreme_berry 4y agoYou wouldn’t believe how many near-FAANGS have hundreds of large backend services on Python without any issues and from times where typing was in docstrings.
- baisq 4y agoBecause they have insane amounts of money that they can throw at the machines.
- CraigJPerry 4y agoI was curious so i had a bash at comparing the cost of just buying another server to throw at the problem vs telling a FAANG dev to optimise the code. A dedicated 40core / 6Tb server is around $2k but will be amortized over the years of its life. It needs power, cooling, someone to install it in a rack, someone to recycle it afterwards, ..., around $175/yr A FAANG dev varies wildly but $400k seems fair-ish (given how many have TC > 750k). So that's about 12 hours of time optimising the code vs throwing another 40c / 6Tb machine at the problem for 365 days. The big cost i'm missing out of both the server and the developer is the building they work in. What's the recharge for a desk at a FAANG, $150k/yr ? I have no idea how much a rack slot works out at. Unless i've screwed up the figures anywhere, we should probably all be looking at replacing Python with Ruby if we can squeeze more developer productivity!
- ramchip 4y agoAdding hardware doesn't improve single-request performance, so slow stacks can require a bunch of optimizing or caching work that wouldn't be needed on a faster one. At some point it also impacts productivity when the test suite is slow, the app takes a long time to restart, etc.
- n8ta 4y agoPrint doesn't have to be re resolved on every access... Not sure about python but many interpreters do a resolution pass that matches declarations and usages (and decides where data lives, stack, heap, virtual register, whatever)
- SnowflakeOnIce 4y agoIn Python semantics, indeed, 'print' does need to be looked up each time!
- n8ta 4y agoSurely in the common case this can be optimized away??? Could you add some detail?
- SnowflakeOnIce 4y ago'print' is a global function that can be overridden by user code. The most obvious way to do this would be to define a new function named 'print', but it could also be overridden through less direct means, like from an 'eval' call, manually manipulating the global variables mapping ('globals()'), or by manipulating the '__builtins__' mapping. Maybe this happens in another thread! Statically reasoning about all this (as would be required for optimization) is difficult. Not totally impossible, but not something that the canonical CPython implementation tries to do.
- DeathArrow 4y agoWhat's wrong with using the right tool for the right job? Python for utility scripts, Javascript for Web frontend, C and C++ for system programming, C# for Web backend, R for statistical stuff and data analysis? It seems to me some guys learned a language suited to a thing and instead of learning other languages better fitted for other purposes, they push for their one and only language to be used everywhere, resulting in delays and financial losses. It's not very hard to learn another language. Or, if you are that lazy, you can stay with the language you know and use it for what was intended.
- supreme_berry 4y ago
- ReflectedImage 4y agoPython has domain on web backend, statistical stuff and data analysis nowadays.
- rhodysurf 4y agoEspecially cuz it allows you to easily write web backends using its awesome scientific and statistical/data science libraries. There is no other language that lets you build web services on dynamic data calculations like that.
- peatmoss 4y agoDuring Perl’s hegemony as The Glue Language, I feel like the folk wisdom was: “Performance is a virtue; if Perl ceases to be good enough, or you need to write ‘serious’ software rewrite in C.” And during Python’s ascension, the common narrative shifted very slightly: “Performance is a virtue, but developer productivity is a virtue too. Plus, you can drop to C to write performance critical portions.” Then for our brief all-consuming affair with Ruby, the wisdom shifted more radically: “Developer productivity is paramount. Any language that delivers computational performance is suspect from a developer productivity standpoint.” But looking at “high-level” languages (i.e. languages that provide developer productivity enhancing abstraction), we can rewind the clock to look at language families that evolved during more resource-constrained times. Those languages, the lisps, schemes, smalltalks, etc. are now really, really fast compared to Python, and rarely require developers to shift to alternative paradigms (e.g. dropping to C) just to deliver acceptable performance. Perl and Python exploded right at the time that Lisp/Scheme hadn’t quite shaken the myth that they were slow, with Python/Perl achieving acceptable performance by having dropped to C most of the time. Now the adoption moat is the wealth of libraries that exist for Python—and it’s a hell of a big moat. If I were a billionaire, I’d hire a team of software developers to systematically review libraries that were exemplars in various languages, and write / improve idiomatic, performant, stylistically consistent versions in something modern like Racket. I’d like to imagine that someone would use those things :-)
- edflsafoiewq 4y agoPerl/Python/Ruby grew up in the 90s, the "Bubble economy" of the single core performance world, the likes of which had never and probably will never be seen again on the face of the Earth. In the post-Bubble world, throwing out 90% of your performance before you even start writing code, especially when the same dynamic features could be delivered via JIT without the cost, seems crazy.
- rockyj 4y agoSo true, excellent point! I just do not understand startups choosing Python/Ruby in 2022 when you can get most of the features, type safety, concurrency, async and 5 times more speed in other languages.
- g42gregory 4y ago15 years ago I remember reading Guido van Rossum saying that Python is a connector language and if you need performance, just drop into C and write/use a C module. I thought it was crazy at the time, but now I see that he was absolutely right. It took a while, but now Python has a high-performing C module for pretty much every task.
- chrisseaton 4y agoBut these don’t compose right? Each is a black-box to each other? A black-box add and a black-box multiply don’t fuse.
- dekhn 4y agoC and Python are not black boxes to each other. The entire python interpreter is literally a C API. You can create pyobjects, add heterogenous PyObjects to PyLists, etc. So evewrything in Python can be introspected from C. Turned around, Python has arbitrary access into the C programming space (really, the UNIX or Windows process it's running inside), so long as it has access to headers or other type info it can see C with more than black box info. Most python numerics is implement in numpy; the low levels of numpy are actually (or were) effectively a C API implementing multiple dimension arrays, with a python wrapper.
- klyrs 4y agoYou're talking past chrisseaton's point here. If you want two C extensions to interoperate with bare-metal performance, you can't just do from lib1 import makedata from lib2 import processdata data = makedata() print(processdata(data)) Because makedata needs to provide a c->py bridge and processdata needs a py->c bridge, so your process inherently has python in the middle unless lib2 has intimate knowledge of lib1. It can absolutely be done (I've written plenty of c extensions that handle numpy arrays, for example) but if somebody hasn't done the work, you don't get it for free. If your c extension expects a list of lists of floats, the numpy array totally supports that interface... but (last I checked) the overhead there is way slower than calling list(map(list, data)) and throwing that into your numpy-naive c extension.
- boringg 4y agoWhat does "Modern" python even mean?
- digisign 4y agoFocuses on 3.8+, but 3.7 has another year of life in it.
- didip 4y agoIf you are building server-side applications using Python 3 and async API and if you didn't use https://github.com/MagicStack/uvloop https://github.com/MagicStack/uvloop, you are missing out on performance big time. Also, if you happen to build microservices, don't forget to try PyPy, that's another easy performance booster (if it's compatible to your app).
- mrslave 4y ago> if it's compatible to your app Every time I experiment with PyPy (on a set of non-trivial web services) I encounter at least one incompatibility with PyPy in the dependency tree and leave disappointed.
- deleted 4y ago[deleted]
- atoav 4y agoJust recently I replaced an internal library (classes for User and a list of users, that need serialization from and to json) with a Python library written in Rust (using pyo3 and maturin). I did not do it for performance considerations, but because I felt the type system of Rust could give me better guarantuees here (and it did, I found multiple bugs this way). On top the performance gain was noticible, although I did not bother to benchmark it. There are still some gotchas in the conversion from Python types to Rust (e.g. a function which takes a Vec<String> will happily accept a str and produce a vector of the str characters. This is common behaviour in Python where I tend to do a isinstance(value, str): value = [value] to deal with this.