News for hackers. Stuff that matters.
Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Monday December 11, 2023 @07:20AM (#38599955)
Python rather happened to have a language design and C API design that doesn't allow a simple performant implementation. Even PyPy was not that fast compared to other JIT implementations, while its C API support was always subpar. It is easy to say that Python should trade them off for performance, but they are one of the key reasons for Python's success after all.
Re:How Many Lines of C It Takes to Execute a and B in Python? by whizzter on Monday December 11, 2023 @07:50AM (#38600192)
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)

Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Monday December 11, 2023 @08:08AM (#38600309)
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 including ordinary objects (and customized objects that look like ordinary objects, e.g. `list` subclasses). That alone complicates a lot of things.
Re:How Many Lines of C It Takes to Execute a and B in Python? by masklinn on Monday December 11, 2023 @08:49AM (#38600595)
It’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).

Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Monday December 11, 2023 @09:24AM (#38600879)
`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...
Re:How Many Lines of C It Takes to Execute a and B in Python? by Sesse__ on Monday December 11, 2023 @12:11PM (#38602368)
Also, you have craziness like quite regular iteration being implemented using exceptions, which are not exactly trivial to optimize.
Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Monday December 11, 2023 @11:10PM (#38609342)
If 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.
Re:How Many Lines of C It Takes to Execute a and B in Python? by Sesse__ on Wednesday December 13, 2023 @01:48AM (#38623552)
If 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.
Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Wednesday December 13, 2023 @05:18AM (#38625208)
I 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

Re:How Many Lines of C It Takes to Execute a and B in Python? by jerf on Monday December 11, 2023 @01:26PM (#38603397)
In 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.

Re:How Many Lines of C It Takes to Execute a and B in Python? by steveklabnik on Monday December 11, 2023 @03:33PM (#38605136)
I think this is really complicated. I do believe that it is important to separate language from implementation, and it is also true that different implementations can have different performance profiles. It is also true that the semantics of the language can effectively require specific implementation details that can affect performance either way. So there's always gonna be bounds to any particular languages' ability to be faster.

That is why the "as fast as C with the sufficiently smart compiler" never truly came to pass in a general sense, even if many languages that were slow to start have gotten way faster with better implementations.

Re:How Many Lines of C It Takes to Execute a and B in Python? by jerf on Tuesday December 12, 2023 @09:32AM (#38612594)
It certainly is complicated.

And I'd love to peer in to some alternate universe that has an optimal Python interpreter (can't say "the" optimal because it's really a complicated frontier rather than a single point) that runs on our hardware and see what it looks like. Maybe even on multiple points on that frontier.

What really is the limit? I struggle to imagine what a C-speed Python interpreter could even look like, but is there some conceivable program that runs it at, say, half the speed? What even is the limit? What techniques would such a program use that would surprise us and be new and perhaps useful other places?

Or are actually pretty close to that frontier now?

To be honest, given the way optimizations tend to work, the answer probably is that we are relatively close today. They tend to have diminishing returns.

But I don't know. Is there some execution model nobody's thought of, or that has been thought of but simply hasn't had the effort invested to make it pay off, that would make huge gains? I can't prove there isn't. (In fact it's not far off junior-level computer science to prove that you can't prove it.)

Re:How Many Lines of C It Takes to Execute a and B in Python? by steveklabnik on Tuesday December 12, 2023 @12:18PM (#38615206)
Yep, that'd be a lot of fun. I think back to the stuff that used to go on to optimize lua: http://lua-users.org/lists/lua-l/2011-02/msg00742.html

I also suspect that we are relatively close, and there are diminishing returns. I suspect that you would have to start the language design with this goal in mind, and then balance out performance concerns with certain features.

Re:How Many Lines of C It Takes to Execute a and B in Python? by slaymaker1907 on Monday December 11, 2023 @02:36PM (#38604406)
You could add in continuation support as well as unbounded stack size to make it even more difficult to implement efficiently. There are tricks these days for implementing continuations somewhat efficiently (and by somewhat, I mean that they're only 3-5x slower than explicit continuation passing with lambdas), but these tricks largely don't work in WASM without doing something extreme like completely ignoring the WASM stack and storing return addresses/current continuation on the WASM heap.

Unbounded stack size is similarly difficult for WASM because like before, you have to be very careful about using the WASM stack.

Even with C++, you basically need to drop down to intrinsics or assembly to make full use of SIMD.

Re:How Many Lines of C It Takes to Execute a and B in Python? by andai on Monday December 11, 2023 @02:35PM (#38604384)
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?

Re:How Many Lines of C It Takes to Execute a and B in Python? by AlotOfReading on Monday December 11, 2023 @03:29PM (#38605072)
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 Wren rather than Python, and people like Python specifically because of the ecosystem that's built up around that dynamicism over the years.

Re:How Many Lines of C It Takes to Execute a and B in Python? by pjmlp on Tuesday December 12, 2023 @02:28AM (#38610678)
Smalltalk, Common Lisp and SELF are just as dynamic if not more, with a JIT.
Re:How Many Lines of C It Takes to Execute a and B in Python? by ngcc_hk on Thursday December 14, 2023 @05:50PM (#38648488)
That is a question in my mind throughout reading this discussion.

How about objective c vs swift from a dynamic at least type vs static one. Can swift be glue.

Re:How Many Lines of C It Takes to Execute a and B in Python? by whizzter on Wednesday December 13, 2023 @05:33PM (#38635142)
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 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 and B in Python? by lifthrasiir on Wednesday December 13, 2023 @11:20PM (#38637738)
I should clarify that LuaJIT is not universally used either, because LuaJIT doesn't support anything after Lua 5.1 among other reasons. People often claim that Lua is a relatively speedy language due to the existence of LuaJIT but it's not entirely correct.
Re:How Many Lines of C It Takes to Execute a and B in Python? by whizzter on Saturday December 16, 2023 @05:51AM (#38663325)
It DIDN'T because Mike Pall(LuaJIT author) was on a hiatus from develoment. He's resumed work since and many 5.2+ features has been incorporated now even if the biggest language-turd (1: _ENV) is explicitly incompatible.

The problem is that _ENV explicitly exposes the lexical environment as regular objects prohibiting optimizations, even JavaScript has _removed_ a similar feature (2: the with statement) when running in "strict" mode to simplify optimizations.

LuaJIT _could_ implement the _ENV blocks but it'd seep into large parts of the codebase as ugly special cases that'd slow down all code in related contexts (thus possibly breaking much performance for code in seemingly unrelated places to where _ENV exists).

To compare from an implementation optimization perspective, exposing _ENV is actually __worse__ than what CPython has with the GIL for example.

Luckily "with"-statements in JS was seldomly used so implementers ignored it's existence as long as it's not used(but still have to consider it in implementations, thus adding more workload), but it's an wart that will kill many optimizations if used.

For most practical purposes most people are fine without "with" or _ENV and the languages are fast enough.

https://luajit.org/extensions.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Re:How Many Lines of C It Takes to Execute a and B in Python? by steveklabnik on Monday December 11, 2023 @03:54PM (#38605373)
I am going to answer this question in a roundabout way: first showing examples of different code gen in Rust, because it is more straightforward, but then I will reach for an example with Ruby, because I know it better than Python, but I believe it is similar enough that you will get the gist.

If I write a function like this in Rust:

  pub fn add(x: i32, y: i32) -> i32 {
      x + y 
  }
this will compile to this assembly (on x86_64):

  add:
    leal (%rdi,%rsi), %eax
    retq
two instructions. This is because in Rust, free functions exist, have a name, and they are called by name. There's an additional twist here though too, let's check it out in debug mode, with optimizations off:

  add:
    subq $24, %rsp
    movl %edi, 16(%rsp)
    movl %esi, 20(%rsp)
    addl %esi, %edi
    movl %edi, 12(%rsp)
    seto %al
    testb $1, %al
    jne .LBB0_2
    movl 12(%rsp), %eax
    addq $24, %rsp
    retq

  .LBB0_2:
    leaq str.0(%rip), %rdi
    leaq .L__unnamed_1(%rip), %rdx
    movq core::panicking::panic@GOTPCREL(%rip), %rax
    movl $28, %esi
    callq *%rax
    ud2
There's a few things going on here, but the core of it is that in Rust, in debug mode, overflow of addition is checked, but in release mode, wrapping is okay, and so the compiler can eliminate the error path. This is an example of language semantics dictating particular implementation: if I require overflow checks, I am going to get more code, because I have to perform the check. If I do not require the checks, I get less code, because I do not perform the checks. (Where this gets more interesting is in larger examples where the checks get elided because the compiler can prove they aren't necessary, but this is already a tangent of a tangent.)

In Ruby, there are no free functions. If I write a similar add function:

  def add(x, y)
    x + y
  end
This function is not a free function: it is a new private method on the Object class. When you invoke a function in Ruby, it's not like Rust, where you simply find the function with the name you're invoking, and then call it. You instead perform "method lookup," which has some details I will elide, but for the purposes of this discussion, the idea is that you first look at the receiver to see if it has the add method defined, and then if it does not, you look at the receivers' parent class, and if it's not there, you keep going until you hit the top of the hierarchy. Once the method definition is found, you then invoke it.

Now, it's not as if Rust doesn't also have method lookup (though the algorithm is entirely different), but Rust's design means that method lookup (in the vast majority of cases) is a compile-time thing: the lookup happens while you're building the software, and then at runtime, it simply calls the function that you found.

So why can't Ruby run method lookup at compile time? Well, for one, I left out an important second step: Ruby provides a method called method_missing, as a metaprogramming tool. What this means is, if we look the whole way up the object hierarchy and do not find a method named add, we will then re-traverse the entire ancestor tree again, instead invoking each class's method_missing method on the way. method missing takes the name of the method that was trying to be called, the arguments to it, and any block passed to it, and you can then do stuff to figure out if you want to handle this. This means that, even if no add function is defined, it still may be possible for the call to succeed, thanks to a method_missing handler.

Okay well why can't we do that at compile time? Well, Ruby also lets you redefine functions at runtime at basically any time. The define_method method can be called and generate a method on anything, anywhere you want, for whatever reason. You could do this based on user input, even! And yes, that would be a terrible idea, and you probably shouldn't do it, but the implementation of the language requires at least some sort of runtime computation to pull this off in the general case.

Now, I also want to point out that in my understanding, there's caching on method lookup, so that can help reduce the cost in many scenarios. But the point stands that the language has features that Rust does not, and those features mean that certain things must be more expensive than languages that do not have those features.

> can we can imagine an alternate universe Python that's as close as possible to our Python, except really fast? What would be different?

We could, but you lose compatibility with most Python code, and so you're effectively creating a new language. People do try this though, Mojo being an example of this very recently. I am excited to see how it goes.

Re:How Many Lines of C It Takes to Execute a and B in Python? by andai on Monday December 11, 2023 @04:38PM (#38605867)
Thanks. That's fascinating about Ruby, I'll have to look into that.

I'm not an expert on Python but I don't see how Python is significantly more dynamic than e.g. JavaScript. I think PyPy and JS performance is comparable (or at least within the same order of magnitude), so I think it largely comes down to implementation, i.e. prioritizing performance.

I think if it had been Python (or Ruby for that matter) in the browser instead of JS, it would run about as fast as JS does today.

Re:How Many Lines of C It Takes to Execute a and B in Python? by lmm on Monday December 11, 2023 @05:28PM (#38606342)
> I'm not an expert on Python but I don't see how Python is significantly more dynamic than e.g. JavaScript.

JS has much less in the way of magic methods that can affect "normal" object behaviour, and it doesn't have metaclasses in the way that Python does at all. Most of this customization goes unused most of the time, but the runtime still has to handle it in case it's being used this time.

Re:How Many Lines of C It Takes to Execute a and B in Python? by pjmlp on Tuesday December 12, 2023 @02:31AM (#38610692)
You should have added Ruby JITs into the explanation, with the related generated Assembly code. :)
Re:How Many Lines of C It Takes to Execute a and B in Python? by steveklabnik on Tuesday December 12, 2023 @12:19PM (#38615222)
Yeah that would be fun but I don't have a Ruby environment installed and the comment is already getting very long; if I ever turn this into a blog post someday maybe I will add that :)
Re:How Many Lines of C It Takes to Execute a and B in Python? by igouy on Tuesday December 12, 2023 @06:16PM (#38620180)
You just happened to post a day after "Ruby 3.3.0-Rc1 Released" "many performance improvements especially YJIT"

https://news.ycombinator.com/item?id=38599293

Re:How Many Lines of C It Takes to Execute a and B in Python? by esafak on Monday December 11, 2023 @02:18PM (#38604159)
"Tech debt doesn't matter!"

"We're going to redesign it the right way after we get this version out the door!"

Re:How Many Lines of C It Takes to Execute a and B in Python? by wongarsu on Monday December 11, 2023 @03:34PM (#38605151)
Arguably those redesigns might have happened if the Python 2->3 transition hadn't received a decade of extremely vocal pushback
Re:How Many Lines of C It Takes to Execute a and B in Python? by fullspectrumdev on Monday December 11, 2023 @05:22PM (#38606280)
What’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.

Re:How Many Lines of C It Takes to Execute a and B in Python? by pwdisswordfishc on Tuesday December 12, 2023 @09:55AM (#38612930)
It’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).
Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Monday December 11, 2023 @09:02PM (#38608348)
> In 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 think "slow language" never meant that way. It was more like a counterpoint to the claim that there are inherent classes of languages in terms of performance, so that some language is (say) 100x or 1000x slower than others in any circumstances. This is not true, even for Python. Most languages with enough optimization works can be made performant enough that it's no slower than 10x C. But once you've got to that point, it can take disproportionally more works to optimize further depending on specific designs.

> 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.

Ironically, Python's relative slowness came from its uniform design which is generally a good thing. This is distinct from a TCL-style "everything is a string" you've said, because the uniform design had a good intent by its own.

If you have used Python long enough you may know that Python originally had two types of classes---there was a transition period where you had to write `class Foo(object):` to get the newer version. Python wanted to remove a blur between builtin objects and user objects and eventually did so. But no one at that time knew that the blur is actually a good thing for optimization. Python tried to be a good language and is suffering today as a result.

Re:How Many Lines of C It Takes to Execute a and B in Python? by jerf on Tuesday December 12, 2023 @09:36AM (#38612644)
"If you have used Python long enough you may know that Python originally had two types of classes"

Yes, because that's also the era where this claim was flying around.

I'd say that each individual person may have their own read on what the claim meant, but certainly the way it was deployed at anyone who vaguely complained that Python was kind of slow shows that plenty of people in practice read it as I've described... that if we just wait long enough and put enough work into it, there would be no performance difference between Python and C. In 2023 we can look back with the perspective that this seems to be obviously false, so they couldn't possible have meant that, but they didn't have that perspective, and so yes they can have meant that. "Sufficiently smart compiler" was just starting to be a derogatory term. I also remember and lightly participated in the c2.com discussions on that, which may also contribute to my point that, yes, there definitely were people who truly believed "sufficiently smart compilers" could exist and were just a matter of time.

As for proportions, it's impossible to tell. Internet discussions (yea verily including this very one) in general are difficult to ascertain that from because almost by definition only outliers are participating in the discussion at all. Obviously by bulk of programmers, most programmers had simply never considered the question at all.

Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Tuesday December 12, 2023 @11:52PM (#38622822)
Yeah, I agree everyone may have different anecdotes, for my case though I heard more of the "Python as glue code" arguments and never heard that Python proper can be as fast as C. I've used Python since 2.3, so maybe that argument was more prevalent before?
Re:How Many Lines of C It Takes to Execute a and B in Python? by ngcc_hk on Thursday December 14, 2023 @05:45PM (#38648439)
Same here. Never heard of python can be fast. It is not what it is originally for.
Re:How Many Lines of C It Takes to Execute a and B in Python? by igouy on Tuesday December 12, 2023 @01:26PM (#38616232)
> Most languages with enough optimization works can be made performant enough that it's no slower than 10x C.

Maybe if we take the slowest C program, CPython no slower than 50x ?

https://benchmarksgame-team.pages.debian.net/benchmarksgame/...

Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Tuesday December 12, 2023 @11:08PM (#38622534)
CPython had many other concerns besides from performance, and PyPy is much closer to my quote I believe.
Re:How Many Lines of C It Takes to Execute a and B in Python? by igouy on Wednesday December 13, 2023 @02:32PM (#38632984)
fwiw "How many lines of C it takes to execute a + b in Python" was explicitly about CPython.

fwiw PyPy doesn't seem to have been released in "the late 1990s".

Re:How Many Lines of C It Takes to Execute a and B in Python? by spacechild1 on Monday December 11, 2023 @01:53PM (#38603809)
Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Monday December 11, 2023 @11:09PM (#38609326)
They reside in metatables, which are only optionally linked to ordinary objects. Python magic methods are a part of the core object protocol.
Re:How Many Lines of C It Takes to Execute a and B in Python? by pjmlp on Tuesday December 12, 2023 @02:26AM (#38610658)
Smalltalk, 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.

Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Tuesday December 12, 2023 @02:34AM (#38610719)
Is 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.
Re:How Many Lines of C It Takes to Execute a and B in Python? by pjmlp on Tuesday December 12, 2023 @06:58AM (#38611085)
Better than CPython, regardless of how you put it.
Re:How Many Lines of C It Takes to Execute a and B in Python? by igouy on Tuesday December 12, 2023 @01:11PM (#38616000)
Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Tuesday December 12, 2023 @11:10PM (#38622543)
Exactly. Comparing with CPython is too easy and arguably unfair, Smalltalk should be compared with JS instead and I think it has no chance in that setting.
Re:How Many Lines of C It Takes to Execute a and B in Python? by pjmlp on Wednesday December 13, 2023 @02:41AM (#38623870)
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.

Re:How Many Lines of C It Takes to Execute a and B in Python? by lifthrasiir on Wednesday December 13, 2023 @05:02AM (#38625068)
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 Smalltalk with another well-optimized JIT implementation instead---in this case JS.

Re:How Many Lines of C It Takes to Execute a and B in Python? by whizzter on Wednesday December 13, 2023 @05:48PM (#38635290)
Not much in the Python language is categorically worse than features within JS when it comes to JIT creation.

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 and B in Python? by pjmlp on Wednesday December 13, 2023 @02:39AM (#38623846)
CPython is the one being compared here, and besides Pharo, better use an industrial strength one, like Cincom Smalltalk.
Re:How Many Lines of C It Takes to Execute a and B in Python? by igouy on Wednesday December 13, 2023 @02:53PM (#38633306)
Cincom Smalltalk is the one being compared.
Re:How Many Lines of C It Takes to Execute a and B in Python? by igouy on Tuesday December 12, 2023 @01:08PM (#38615949)
So if one was "Prototyping a Real-Time Embedded System in Smalltalk" one might 'improve frequently invoked methods by recoding, possibly as “primitive” functions in a lower level language such as C or assembler'.

https://dl.acm.org/doi/pdf/10.1145/74878.74904

Re:How Many Lines of C It Takes to Execute a and B in Python? by pjmlp on Wednesday December 13, 2023 @02:42AM (#38623883)
CPython is up to date with one specific Smalltalk implementation in 1987, when an ESP32 has better hardware resources than those systems, what a great achievement!
Re:How Many Lines of C It Takes to Execute a and B in Python? by igouy on Wednesday December 13, 2023 @02:51PM (#38633285)
One way Smalltalk was made a better fit for number crunching: primitives.
Re:How Many Lines of C It Takes to Execute a and B in Python? by whizzter on Wednesday December 13, 2023 @05:21PM (#38635037)
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 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 and B in Python? by lifthrasiir on Wednesday December 13, 2023 @11:16PM (#38637716)
Instead 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

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 and B in Python? by KMag on Monday December 11, 2023 @03:32PM (#38605115)
> 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.

Re:How Many Lines of C It Takes to Execute a and B in Python? by ngcc_hk on Thursday December 14, 2023 @06:07PM (#38648673)
Finally 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.