Earlier quoted context omitted.
vs CPython https://benchmarksgame-team.pages.debian.net/benchmarksgame/... vs Node https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
CPython is the one being compared here, and besides Pharo, better use an industrial strength one, like Cincom Smalltalk.
How many lines of C it takes to execute a + b in Python
211–220 of 224 posts
Re: How many lines of C it takes to execute a + b in Python
#212Earlier quoted context omitted.
If you read the article you can see that some codepaths can invoke Malloc with all the follow-on effects like Kernel boundary crossings that this implies, it's thus quite random.
It would still make sense to give a number or a range.
Not only is there branches to a ton of special things but also macros that hides even more lines (IncRef/DecRef probably has a lot of magic behind there).
Re: How many lines of C it takes to execute a + b in Python
#213Earlier quoted context omitted.
If you read the article you can see that some codepaths can invoke Malloc with all the follow-on effects like Kernel boundary crossings that this implies, it's thus quite random.
Since malloc() is a standard C library function, it would be okay to not count its implementation (which isn’t necessarily written in C).
Re: How many lines of C it takes to execute a + b in Python
#214Earlier quoted context omitted.
The semantic issues of making a performant Python language implementation are more or less exactly the same as for JS and Lua, optimizing Ruby seems to possibly have even more "magic" that needs patching but we've seen the Shopify team get cracking on that (it includes MaximeCB that did HiggsJS). PyPy is in many aspects to be rated as a research project that tried a novel approach to reduce the workload compared to t…
Unfortunately Python's innard is much more complicated than most expectations. You have named JS and Lua, but those languages never have "magic" methods---JS instead has prototypes and more recently proxies, while Lua has metatables. Ordinary objects aren't magic in this sense, and conversely magical objects are generally deliberate choices in those languages. But Python's magic `__dunder__` methods are everywhere in…
First off is the value model, the Python runtime handles ALL values as objects and that's fine for an initial naive runtime. All fast/modern language runtimes however use value models/encodings that fits "fast" values directly into machine register at the lowest level.
V8 has(had?) "small-ints" and objects (doubles,strings,etc) by setting the lowest bit in a register for pointers and otherwise dealing with them as numbers. So a+b when JIT'ed has a check (or stored knowledge from a previous opertion to elide the check) that both a and b are integers, if that is true then the actual addition is one single machine addition. if that ISN'T true then a more complex machinery is invoked that could methods like double-dispatch to see if more complex processing (like a "magic" method) is needed. This is how as JS engine handles that + behaves differently between numbers, strings, BigInt's, Date object's,etc.
(Other JS engines and LuaJIT use something called NaN/NuN tagging that also allows for quick passing of numbers w/o allocations and only a few small extra checks)
Re-implementing Python, you'd probably choose a small-int optimization (to better support Pythons seamless bigints) for values, put a runtime specific magic to the number add and make some kind of hook that detects writes to it from user code. Patching that from user code would trigger de-optimizations but for most applicataions it could continue running with optimized paths.
And even with larger objects (like heap-allocated BigInt's) a JS runtime can use inline caching to direct the runtime to fast direct dispatches, and then teams like the V8 team can detect commonly used objects that are often used and create fast-paths. A list addition for example will use common "slow" paths for dispatch but that's ok since it's an inherently slow operation that often involves allocations of some sort so the _relative_ overhead is fairly small in the big picture.
All this naturally assumes that you have the machinery in place, once in place though you can make simple code (numeric additions) fast while retaining magic for more complex objects (bigint, list,string,etc).
Tl;Dr; once you have that kind of optimizing in place, expensive processing can be allowed in special cases in slow paths thanks to type-guards, but 95% of the code will run the fast paths and having that handling in places with speed will give you most wins.
Re: How many lines of C it takes to execute a + b in Python
#215Earlier quoted context omitted.
Can someone explain what exactly it is about Python's design that makes it slow? What changes would have to be made to speed it up? Obviously changing its core design now would break things, but my question is, can we can imagine an alternate universe Python that's as close as possible to our Python, except really fast? What would be different?
It's the whole design. Every object creates allocation/gc overhead, bytecode dispatch is a major bottleneck, attribute lookup is expensive, objects are expensive, namespaces are expensive, etc. You can change things internally (e.g. optimizing opcode parsing), fixed object layouts, restricting mutability, converting everything to predictable array accesses, but you'll likely just end up with something like Lua or Wre…
The huge issue is that a big selling point for Python was the easy C-api integration providing lots of useful functionality via libraries now works as a chain that limits how many changes can be made (see any GIL-removal discussion).
The most sane way forward would be to mandate a conversion to a future-proof C-api (PyPy has already designed an initial one iirc that's tested and also has CPython support) that packages would convert to over time.
CPython will probably never go away due to many private users of the old api, but beginning the work towards implementation independancy in the package ecosystem at large could allow _language compatible_ runtimes with V8/JSCore/LuaJIT-like performance for most new projects.
It all depends on the entire community though and that in turn depends on the goodwill of the CPython team to support this.
Re: How many lines of C it takes to execute a + b in Python
#216Earlier quoted context omitted.
Why should it be compared? We are not comparing Smalltalk JITs to Javascript JITs. The whole point of this conversation is CPython refusing to add one, and the lame excuses regarding its dynamic capabilities, when more dynamic languages have had a JIT for decades.
First, because CPython had more concerns than what Smalltalk implementations have, so such comparison would be unfair to Python. (See my topmost comment for example.) Second, my question was about the possibility that Smalltalk and others were unbearably slow without JIT, so JIT was not a matter of choice for them. I'm not aware of Smalltalk implementations that don't have JIT, so it would be easier to compare Smallt…
My thesis work was on AOT JS compilation, in it I refer to a bunch of experimental Python runtimes, Self,etc and the main issues in all these papers were basically the of same kind.
Heck, even PyPy exists and iirc when it comes to the core language is almost entirely compatible (except for code that relies on ref-counting semantics but that code should apparently big fixed anyhow).
https://www.pypy.org/compat.html
The real summary is: CPython is a turd in many ways, the old C api holds it back and the community hasn't put the effort into using cross-implementation compatible C-bindings instead making PyPy or others a second class citizen.
Re: How many lines of C it takes to execute a + b in Python
#217Earlier quoted context omitted.
Unfortunately Python's innard is much more complicated than most expectations. You have named JS and Lua, but those languages never have "magic" methods---JS instead has prototypes and more recently proxies, while Lua has metatables. Ordinary objects aren't magic in this sense, and conversely magical objects are generally deliberate choices in those languages. But Python's magic `__dunder__` methods are everywhere in…
Magic methods are not that "hard" to optimize (as long as you don't overload the add,etc operators of f.ex. the Number class in JS). I'm gonna use numbers and addition as an example here. First off is the value model, the Python runtime handles ALL values as objects and that's fine for an initial naive runtime. All fast/modern language runtimes however use value models/encodings that fits "fast" values directly into…
Modern tracing JIT engines indeed work by (heavy) specialization, often using multiple underlying representations for single runtime type. I think V8 has at least four Array representations? After many enough specializations it is possible to get a comparable performance even for Python. The question is how many, however.
For a long time, most dynamically typed languages and implementations didn't even try to do JIT because of its high upfront cost. The cost is much lower today---yet still not insignificant enough to say it's no-brainer to do so---, but that fact was not that obvious 20 years ago. Ruby was also one of them, and YJIT was only possible thanks to Shopify's initial works. Given an assumption that JIT is not feasible, both CPython developers and users did a lot of things that further complicate eventual JIT implementations. C API is one, which is indeed one of the major concern for CPython, but a highly customized user class is another. Herein lies the problem:
> Magic methods are not that "hard" to optimize (as long as you don't overload the add,etc operators of f.ex. the Number class in JS).
Indeed, it is very unusual to subclass `Number` in JS, however it is less unusual to subclass `int` in Python, because it is allowed and Python made it convenient. I still think a majority of `int` will use the built-in class and not subclasses, but if it's the only concern, Psyco [1] should have been much popular when it came out because it should have handled such cases perfectly. In reality Psyco was not enough, hence PyPy.
[1] https://psyco.sourceforge.net/introduction.html
At this point I want to clarify that magic methods in Python are much more than mere operator overloading. For example, properties in JS are more or less direct (`Object.defineProperty` and nowadays a native syntax), but in Python they are implemented via descriptors, which are a nested object with yet another dunder methods. For example this implements the `Foo.bar` property:
class Foo:
class Bar:
def __get__(self, obj, objtype=None): return 42
bar = Bar()
In reality everyone will use `bar = property(lambda self: 42)` or equivalent instead, but that's how it works underneath. And the nested object can do absolutely anything. You can specialize for well-known descriptor types like `property`, but that wouldn't be enough for complex Python codebases. This is why...> This is how as JS engine handles that + behaves differently between numbers, strings, BigInt's, Date object's,etc.
...is not the only thing JS engines do. They also have hidden classes (aka shapes) that are recognized and created in runtime, and I think it was one of innovations pioneered by V8---outside of the PL academia of course. Hidden classes in Python would be more complex than those in JS for this added flexibility and resulting uses. And JS hidden classes are not even that simple to implement.
After decades of JIT not in sight, and a non-trivial amount of work to get a working JIT even after that, it is not unreasonable that CPython didn't try to build JIT for a long time and the current JIT work is still quite conservative (it uses a copy-and-patch compilation to reduce the upfront cost). CPython did do lots of optimizations possible in interpreters though, many things mentioned above are internally cached for performance. One can correctly argue that such optimizations were not steady enough---for example, adaptive opcodes in 3.11 are something Java HotSpot used to do more than 10 years ago.
Re: How many lines of C it takes to execute a + b in Python
#218Earlier quoted context omitted.
It's the whole design. Every object creates allocation/gc overhead, bytecode dispatch is a major bottleneck, attribute lookup is expensive, objects are expensive, namespaces are expensive, etc. You can change things internally (e.g. optimizing opcode parsing), fixed object layouts, restricting mutability, converting everything to predictable array accesses, but you'll likely just end up with something like Lua or Wre…
Iirc the slowness of CPython in all the above mentioned are artifacts of the implementation rather than the language itself. The huge issue is that a big selling point for Python was the easy C-api integration providing lots of useful functionality via libraries now works as a chain that limits how many changes can be made (see any GIL-removal discussion). The most sane way forward would be to mandate a conversion to…
Re: How many lines of C it takes to execute a + b in Python
#219Earlier quoted context omitted.
Basically nobody's goal is to "write a performant web server" either, it's to serve data to customers quickly and efficiently . And that highlights why it may not be worth optimizing the Python web ecosystem. There are so many newer alternatives for that overall goal - Firebase, Amazon Lambda, ditching webapps for native mobile, etc - that it may not make sense to try to optimize an application server unless you work…
Nobody's goal is to serve data to customers quickly and efficiently either. It's more like, get this startup acquired and buy a ranch in New Zealand .
https://news.ycombinator.com/item?id=9777816
Root of the subthread: https://news.ycombinator.com/item?id=9775799
Re: How many lines of C it takes to execute a + b in Python
#220Earlier quoted context omitted.
It would still make sense to give a number or a range.
Depends, I look at it from a performance standpoint when starting to count lines/instructions, not just directly executed code but also how feasible it would be to translate the thing to a JIT for example, the amount is large enough that going to a JIT would yield little (this is why there has been so many Python JIT's that has failed to gain enough performance and hence traction) before mayor architectural fixes are…