This is exactly the kind of content I love to see on HN. But I wonder though how this optimization is related to tail-call optimization? How the interpreter jump table is implemented shouldn't affect how stack frames are created, should it?
Performance of the Python 3.14 tail-call interpreter
101–110 of 180 posts
Re: Performance of the Python 3.14 tail-call interpreter
#102Earlier quoted context omitted.
Show me a C compiler that miscompiles the following code and I'll concede the point: uint32_t add_1(uint32_t a) { a += 1; return a; }
If my misocompile, you mean that it fails the test that a "C expression `a += 1` can be trusted to increment a numeric value", then it is trivial: https://godbolt.org/z/G5dP9dM5q
The (implied) claim is that the C standard has enough sources of undefined behavior that even a simple integer addition can't be relied upon to actually perform integer addition.
But the sources of undefined behavior for integer addition in C are well-known and very clear, and any instruction set that isn't an insane science project is going to have an instruction to add integers.
Thus my comment. Show me a C compiler that takes that code and miscompiles it. I don't care if it returns a constant, spits out an infinite loop, jumps to 0x0000, calls malloc, whatever. Show me a C compiler that takes those four lines of C code and emits something other than an integer addition instruction.
Re: Performance of the Python 3.14 tail-call interpreter
#103Earlier quoted context omitted.
Take a closer look at 'eru's example and my follow-up. He wrote an example where the result of `a+1` isn't necessary, so the compiler doesn't emit an ADDI even though the literal text of the C source contains the substring "a += 1". Your version has the same issue: unsigned int square2(unsigned int num) { unsigned int a = num; a += 1; if (num The return value doesn't depend on `a+1`, so the compiler can optimize it t…
In what world the return value doesn't depends on 'a' in this code? if (num A control dependency is still a dependency
For signed integer addition the compiler is allowed to assume that `(num < (num + 1))` is true, so the comparison can be removed entirely.
Re: Performance of the Python 3.14 tail-call interpreter
#104Earlier quoted context omitted.
That's a fair question. The blog post mentions it brings a 1-5% perf improvement. Which is still significant for CPython. It does not complicate the source because we use a DSL to generate CPython's interpreters. So the only complexity is in autogenerated code, which is usually meant for machine consumption anyways. The other benefit (for us maintainers I guess), is that it compiles way faster and is more debuggable…
There was a plan for a 5x speedup overall looking for funding back in 2022. Then a team with Guido and others involved (and MS backing?) got on the same bandwagon and made some announcements for speeding up CPython a lot. Several releases in, have we seen even a 2x speedup? Or more like 0.2x at best? Not trying to dismiss the interpreter changes - more want to know if those speedup plans were even remotely realistic,…
Re: Performance of the Python 3.14 tail-call interpreter
#105Is it about the layout of where the assembly instructions end up, and spacing around them? Or the CPU pipelining working better? Or...?
Re: Performance of the Python 3.14 tail-call interpreter
#106To clarify: The situation is still not completely understood? It's not just only the computed gotos, but there is some other regression in Clang19? Basically, the difference between clang19.nocg and clang19 is not really clear? Btw, what about some clang18.tc comparison, i.e. Clang18 with the new tail-call interpreter? I wonder how that compares to clang19.tc.
Re: Performance of the Python 3.14 tail-call interpreter
#107Earlier quoted context omitted.
That’s a contrived example but in a serious program there would often be code in between or some level of indirection (e.g. one of those values is a lookup, a macro express, or the result of another function). Nothing about that is cheating, it just says that even C programmers cannot expect to look at the compiled code and see a direct mapping from their source code. Your ability to reason about what’s actually exec…
In what languages can you do that that is not assembly though? The higher level the language is, the "worse" or difficult it gets, perhaps I am not following the thread right.
I think it’s also reflecting the maturity and growth of the industry. A turn of the century programmer could relatively easily find areas where dropping down to assembly was useful, but over the subsequent decades that’s become not only uncommon but often actively harmful: your code hand-optimized for a particular processor is likely slower on newer processors than what a modern compiler emits and is definitely a barrier to portability in an era where not only are ARM and potentially RISC-V of interest but also where code is being run on SIMD units or GPUs. This makes the low-level “portable assembler” idea less useful because there’s less code written in that middle ground when you want either a higher-level representation which gives compilers more flexibility or precise control. For example, cryptography implementers want not just high performance but also rigid control of the emitted code to avoid a compiler optimizing their careful constant-time implementation into a vulnerability.
Re: Performance of the Python 3.14 tail-call interpreter
#108Earlier quoted context omitted.
> All of those look pretty straightforward to me -- again, what assembly would you expect to be emitted in those cases? It is very straightforward indeed, but it is still not mapping primitive operations to direct machine code, but it is forwarding to out-of-line code. Same as operator overloading in other languages. > And here's assembly for C++: https://godbolt.org/z/dedcof9x5 That's just a symptom of allowing the…
> It is very straightforward indeed, but it is still not mapping primitive > operations to direct machine code, but it is forwarding to out-of-line code. > Same as operator overloading in other languages. I am not claiming that C is a collection of assembler macros. There is no expectation that a C compiler emit machine code that has exact 1:1 correspondence with the input source code. > Same as operator overloading…
Until someone calls longjmp() or a signal() is triggered. Extra bonus of fun if it happens to be multithreaded application, or in the middle of a non-rentrant call.
Re: Performance of the Python 3.14 tail-call interpreter
#109Earlier quoted context omitted.
That's a fair question. The blog post mentions it brings a 1-5% perf improvement. Which is still significant for CPython. It does not complicate the source because we use a DSL to generate CPython's interpreters. So the only complexity is in autogenerated code, which is usually meant for machine consumption anyways. The other benefit (for us maintainers I guess), is that it compiles way faster and is more debuggable…
There was a plan for a 5x speedup overall looking for funding back in 2022. Then a team with Guido and others involved (and MS backing?) got on the same bandwagon and made some announcements for speeding up CPython a lot. Several releases in, have we seen even a 2x speedup? Or more like 0.2x at best? Not trying to dismiss the interpreter changes - more want to know if those speedup plans were even remotely realistic,…
Re: Performance of the Python 3.14 tail-call interpreter
#110Earlier quoted context omitted.
> The phrase "C is portable assembly" isn't a claim that each statement gets compiled directly to equivalent machine code. Weasel words. Like a "self driving car" that requires a human driver with constant attention willing to take over within a few hundred milliseconds. People advocate for C and use it in a way that implies they think it can achieve specific machine outcomes, and it usually does .. except when it do…
As a general rule if you're reading a technical discussion and every single participant is using a particular phrase in a way that doesn't make sense to you then you should probably do a quick double-check to make sure you're on the same page. For example, in this discussion about whether C is "portable assembly", you might be tempted to think back to the days of structured programming in assembly using macros. I no…
There's a lot hiding in "more or less". The same kind of example holds for e.g. C# : https://godbolt.org/noscript/csharp ; if you hit "Compile" it'll give you the native binary. If you write "x+1" it'll generate an add .. or be optimized away. Now does that mean it's portable assembler? Absolutely not.
Conversely there's a bunch of things that people expect to do in C, do in real code, but are not in the standard or are undefined or implementation-defined. As well as things that are present in assemblers for various platforms (things like the overflow flag) which aren't accessible from the C language.
What people actually seem to mean by "portable assembler" is "no guardrails". Memory unsafety as a feature.
> Reasonable people can disagree about exactly what transformations are legal, but at that point it's a matter of negotiation
And a matter of CVEs when you lose your negotiation with the compiler. Or less dramatic things like the performance fluctuations under discussion.