8 ms·
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
by whizzter 3y ago
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 the manhours poured into V8,etc. LuaJIT managed with less with a focused language and a really capable lead. (Also I wouldn't be surprised if the PyPy team has also had to make compromises to get some kind of compatibility)
- lifthrasiir 3y agoUnfortunately 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 including ordinary objects (and customized objects that look like ordinary objects, e.g. `list` subclasses). That alone complicates a lot of things.
- masklinn 3y agoIt’s not entirely true that JS does not have magic methods. `valueOf` and `toString` can show up surprisingly deep into the resolution of operations, and recent JS has “well known symbols” to implement or override behaviour. However it is true that this is much, much less extensive than it is in Python. As of 3.12, section 3.3 (“special method names”) of the data model documentation lists 107 entries (although some of them only apply to class protocols, and a handful are duplicates for async versions / context of sone operations).
- lifthrasiir 3y ago`valueOf` and `toString` are indeed fairly complex (and it's fun to consider when both are implemented ;-), but less of concern for tracing JIT engines because you can have efficient type-specialized implementations for most cases. Type specialization in Python is not that huge win...
- Sesse__ 3y agoAlso, you have craziness like quite regular iteration being implemented using exceptions, which are not exactly trivial to optimize.
- lifthrasiir 3y agoIf you want to be return absolutely any value from an iterator, an exception is indeed a reasonable choice though. Python generators came in much later, unlike JS for example.
- Sesse__ 3y agoIf that is indeed a requirement, you let your iterator have an .at_end() test instead of trying to shoehorn it into the return value. Or have something like C++'s std::optional or Rust's Option. Or a special EndOfIteration object that you cannot return from an iterator (is that really so bad?). Iteration is extremely common, and should thus be based on as simple primitives as possible if you want things to go fast.
- lifthrasiir 3y agoI believe the original PEP [1] answers almost all questions. The only option not covered would be probably an "optional" type, but it is even easier to answer: any such type has to be heap-allocated in the Python memory model, so a mere iteration could've caused a lot of redundant memory allocations. [1] https://peps.python.org/pep-0234/#rationale https://peps.python.org/pep-0234/#rationale
- jerf 3y agoIn the late 1990s and early 200xs, there were a lot of claims that there was no such thing as a "slow language", that all languages can be run as quickly as C if you just built a sufficiently smart compiler and/or runtime. I haven't heard anyone make this claim in a while. The inability to speed up Python beyond a certain point despite a lot of clever approaches taken was probably a good chunk of the reason, the remainder being the wall that JS has hit despite the huge effort poured into it where it is still quite distinctly slower than C. If I were designing a language to be slow, but not like stupidly slow just to qualify as an esolang, but where the slowness still contributed to things I could call "features" with a straight face, it would be hard to beat Python. I suppose I could try to mix in more of a TCL-style "everything is a string" and "accidentally" build some features on it that bust the string caching, but that's about all I can think of at this point.
- esafak 3y ago"Tech debt doesn't matter!" "We're going to redesign it the right way after we get this version out the door!"
- wongarsu 3y agoArguably those redesigns might have happened if the Python 2->3 transition hadn't received a decade of extremely vocal pushback
- fullspectrumdev 3y agoWhat’s funny is the breaking change that caused us the most headaches with 2->3 was the changes to fucking print. I’m still finding broken print as a statement instead a function issues in codebases, somehow.
- pwdisswordfishc 3y agoIt’s the easiest one to fix, though. 2to3 does it easily. If it were the only change, we would not be having this conversation. It might have even not deserved a major version bump (given that even minor releases of Python don't conserve backwards compatibility).
- spacechild1 3y agoLua has metamethods: https://www.lua.org/manual/5.3/manual.html#2.4 https://www.lua.org/manual/5.3/manual.html#2.4
- lifthrasiir 3y agoThey reside in metatables, which are only optionally linked to ordinary objects. Python magic methods are a part of the core object protocol.
- pjmlp 3y agoSmalltalk, SELF and Common Lisp are full of dynamic magic, you can in a single function call change the representation of all instances of a given object during program execution, at any given time break into the debugger and change whatever you feel like and resume execution, dynamically load code from the network with side effects on the running program, and many other crazy things. Yet, not only are they in the genesis of JIT compiler research, their JITs are quite good, and their results went directly into JavaScript JITs research.
- lifthrasiir 3y agoIs there any direct comparison though? Because it can be alternatively argued that these dynamic languages needed new JIT techniques for performance, but they especially work well for more constrained languages like JS. I don't think Smalltalk was ever a good fit for number crunching, for example.
- pjmlp 3y agoBetter than CPython, regardless of how you put it. EDIT: Also to note, those languages powered whole graphical single workstations, with microcoded CPUs + JIT. "Efficient implementation of the smalltalk-80 system" https://dl.acm.org/doi/10.1145/800017.800542 https://dl.acm.org/doi/10.1145/800017.800542 https://computerhistory.org/blog/introducing-the-smalltalk-zoo-48-years-of-smalltalk-history-at-chm/ https://computerhistory.org/blog/introducing-the-smalltalk-z... "Self-Confidence: How SELF Became a High-Performance Language" https://www.cs.cornell.edu/courses/cs6120/2020fa/blog/self/ https://www.cs.cornell.edu/courses/cs6120/2020fa/blog/self/
- igouy 3y agovs CPython https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/vw-python3.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... vs Node https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/smalltalk.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
- whizzter 3y agoMagic 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 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.
- lifthrasiir 3y agoInstead of individually replying your comments, let me answer them at once here, because I agree you are correct in principle but think you are still missing my points. 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 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.
- KMag 3y ago> LuaJIT managed with less with a focused language and a really capable lead. It's pretty well established that "Mike Pall" is the pen name for an AI sent from the future for unknown reasons. It disappeared from our light cone due to a rift in causality, presumably because it succeeded in whatever changes it wanted to make in the future.
- ngcc_hk 3y agoFinally got the proof that time travel did exist. But I wonder many like bitcoin might be. Then the pin drop could the whole python is a future ai coming in and prevent us from optimise and squeeze them out. They need the glue language. Ai depend upon it. Hence it is all for ai.