Live data from Hacker News

Performance of the Python 3.14 tail-call interpreter

blog.nelhage.com

11–20 of 180 posts

Re: Performance of the Python 3.14 tail-call interpreter

#11
To 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

#12

This is a very good example of how C is not "close to the machine" or "portable assembly", modern optimizers will do drastic changes to the logic as long as it has no observable effect. As stated in the post: "Thus, we end up in this odd world where clang-19 compiles the computed-goto interpreter “correctly” – in the sense that the resulting binary produces all the same value we expect – but at the same time it produ…

  > This is a very good example of how C is not "close to the machine" or
  > "portable assembly",
C is very much "portable assembly" from the perspective of other systems programming languages of the 80s-90s era. The C expression `a += 1` can be trusted to increment a numeric value, but the same expression in C++ might allocate memory or unwind the call stack or do who knows what. Similarly, `a = "a"` is a simple pointer assignment in C, but in C++ it might allocate memory or [... etc].

The phrase "C is portable assembly" isn't a claim that each statement gets compiled directly to equivalent machine code.

Re: Performance of the Python 3.14 tail-call interpreter

#13
Great article! One detail caught my attention.

In one of the referenced articles, https://simonwillison.net/2025/Feb/13/python-3140a5/, the author wrote: "So 3.14.0a5 scored 1.12 times faster than 3.13 on the benchmark (on my extremely overloaded M2 MacBook Pro)."

I'm quite confused by this. Did the author run the benchmark while the computer was overloaded with other processes? Wouldn't that make the results completely unreliable? I would have thought these benchmarks are conducted in highly controlled environments to eliminate external variables.

Re: Performance of the Python 3.14 tail-call interpreter

#14

Benchmarking is just insanely hard to do well. There are so many things which can mislead you. I recently discovered a way to make an algorithm about 15% faster. At least, that's what all the benchmarks said. At some point I duplicated the faster function in my test harness, but did not call the faster version, just the original slower one... And it was still 15% faster. So code that never executed sped up the origin…

I vaguely remember about some benchmarking project that deliberately randomised these compiler decisions, so that they could give you more stable estimates of how well your code actually performed, and not just how well you won or lost the linker lottery.

Re: Performance of the Python 3.14 tail-call interpreter

#15

This is a very good example of how C is not "close to the machine" or "portable assembly", modern optimizers will do drastic changes to the logic as long as it has no observable effect. As stated in the post: "Thus, we end up in this odd world where clang-19 compiles the computed-goto interpreter “correctly” – in the sense that the resulting binary produces all the same value we expect – but at the same time it produ…

> This is a very good example of how C is not "close to the machine" or > "portable assembly", C is very much "portable assembly" from the perspective of other systems programming languages of the 80s-90s era. The C expression `a += 1` can be trusted to increment a numeric value, but the same expression in C++ might allocate memory or unwind the call stack or do who knows what. Similarly, `a = "a"` is a simple pointe…

> The C expression `a += 1` can be trusted to increment a numeric value, [...]

Have you heard of undefined behaviour?

Re: Performance of the Python 3.14 tail-call interpreter

#16
post #6

Trying to assess the performance of a python build is extremely difficult as there are a lot of build tricks you can do to improve it. Recently the astral folks ran into this showing how the conda-forge build is notable faster than most others: https://github.com/astral-sh/python-build-standalone/pull/54... I'd be interested to know how the tail-call interpreter performs with other build optimisations that exist.

Compare https://donsbot.com/2009/03/09/evolving-faster-haskell-progr...

The author uses a genetic algorithm to try out lots of different compiler and optimisation flag combinations.

Re: Performance of the Python 3.14 tail-call interpreter

#17
post #14

Benchmarking is just insanely hard to do well. There are so many things which can mislead you. I recently discovered a way to make an algorithm about 15% faster. At least, that's what all the benchmarks said. At some point I duplicated the faster function in my test harness, but did not call the faster version, just the original slower one... And it was still 15% faster. So code that never executed sped up the origin…

I vaguely remember about some benchmarking project that deliberately randomised these compiler decisions, so that they could give you more stable estimates of how well your code actually performed, and not just how well you won or lost the linker lottery.

You're probably thinking of "Performance Matters" by Emery Berger, a Strange Loops talk. https://youtube.com/watch?v=r-TLSBdHe1A

Re: Performance of the Python 3.14 tail-call interpreter

#18
post #14

Benchmarking is just insanely hard to do well. There are so many things which can mislead you. I recently discovered a way to make an algorithm about 15% faster. At least, that's what all the benchmarks said. At some point I duplicated the faster function in my test harness, but did not call the faster version, just the original slower one... And it was still 15% faster. So code that never executed sped up the origin…

I vaguely remember about some benchmarking project that deliberately randomised these compiler decisions, so that they could give you more stable estimates of how well your code actually performed, and not just how well you won or lost the linker lottery.

There was Stabilizer [1] which did this, although it is no longer maintained and doesn't work with modern versions of LLVM. I think there is something more current now that automates this, but can't remember what it's called.

[1] https://emeryberger.com/research/stabilizer/

Re: Performance of the Python 3.14 tail-call interpreter

#19
post #15

Earlier quoted context omitted.

> This is a very good example of how C is not "close to the machine" or > "portable assembly", C is very much "portable assembly" from the perspective of other systems programming languages of the 80s-90s era. The C expression `a += 1` can be trusted to increment a numeric value, but the same expression in C++ might allocate memory or unwind the call stack or do who knows what. Similarly, `a = "a"` is a simple pointe…

> The C expression `a += 1` can be trusted to increment a numeric value, [...] Have you heard of undefined behaviour?

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;
  }

Re: Performance of the Python 3.14 tail-call interpreter

#20
post #15

Earlier quoted context omitted.

> The C expression `a += 1` can be trusted to increment a numeric value, [...] Have you heard of undefined behaviour?

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; }

I know that for 'int a' the statement 'a += 1' can give rather surprising results.

And you made a universal statement that 'a += 1' can be trusted. Not just that it can sometimes be trusted. In C++ the code you gave above can also be trusted as far as I can tell. At least as much as the C version.

Post reply on HN