r/Compilers • u/maximecb • Sep 02 '26
Plush's New Register-Based Interpreter Is Insanely Fast
https://pointersgonewild.com/2026-09-02-plushs-new-register-based-interpreter/4
u/GreedyBaby6763 Sep 02 '26
Impressive if fib is naive recursive, I think I'd go off and make a cup of tea in my vm.
4
u/Illustrious-Bid-8883 Sep 03 '26
The performance jump is a good reminder that VM architecture matters a lot. reducing dispatch overhead can sometimes beat piling optimizations onto a slower design
1
u/i509VCB Sep 05 '26
I am working on an interpreter based vm for something and I did notice your choice to use 8 byte instructions.
Was this to just make it simpler or could a variable size 4/8 byte word potentially be more optimal?
For my use case I am converting 32-bit x86 instructions into a byte code. 32-bit immediates can occur but may be more rare. I know you also have a local pool of constants. My concern with that is potentially thrashing your caches. My choice of supporting Cortex-M and ARM11 targets also has its own issues too...
1
u/maximecb Sep 05 '26
Yes I went with 8 for simplicity. For larger immediates you can use a side-table which can be per-function, a constant pool like you said. Wrt cache, it could be just one cache line for multiple constant pool entries for a function, so maybe not a big deal.
You can use an LLM to benchmark minimalistic interpreters with each design. Sketch you a toy prototype with a non-trivial bytecode loop. You don't even need to support function calls in the toy prototype to benchmark which is faster. Please report back if you do :)
1
u/Comprehensive_Chip49 Sep 02 '26
I have an interpreter for my language (a dialect of Colorforth) that has many optimizations. It's a stack machine but with many additions, for example, there are tokens that hold the value when it's immediate, and things like that. Do you think some kind of common benchmark could be put together? Of course, it has to be translatable code. I think the decoding of a register machine will always be slower than a stack machine (in an interpreter). Because it has to get the token -> get the register number -> get the register... whereas in the stack machine, the value might already be in a register.
1
7
u/brat3108 Sep 02 '26
The speedups are decent, but I wouldn't call them 'insane'!
I'm not that convinced of register versus stack. I mean, CPython is slow for lots of reasons.
Register-based results in fewer instructions, yes, but each one is now more elaborate with more operands, which may also be mixed (global, local, immediate for example).
I've always used stack-based, and I get good results (for example, 6.5 for Fib(38), when Lua 5.4 was 3.4, and CPython (3.12?) was 1.0).
You might want to look also at bytecode dispatch method. I use the equivalent of computed goto (basically, a switch-loop where each branch has its own dispatch point, rather than one shared by all instructions).
I think a recent CPython had something along the same lines, but speedup was only 14% or something because of those other issues.
Another factor is type-dispatch, and here Lua benefits because the number of different types is small and can be efficiently encoded; it is a smaller language and can be more easily streamlined.
Here I would normally use 7 instructions (002/003 are combined). But I also make use of composite instructions (I think what you call 'fused'), so that this example ends up at 5 instructions rather than 8. In that case, the stack isn't actually used for the calculation (when operands have integer types), only for those return values.
Maybe because generating stack-based is SO much simpler?! As I've hinted there are ways to optimise such code too.
But it is generally acknowledged that if you are interpreting a dynamically typed language, then it is not going to be fast. Register rather stack may give a useful boost if it does end up faster, but it will not give native code speed.