News for hackers. Stuff that matters.
Re:Building Fast Interpreters in Rust by skybrian on Monday March 04, 2019 @11:47AM (#19302614)
In C, a common way to get good performance without JIT is with bytecode and a giant switch statement. It would be interesting to see if that's viable in Rust. How does performance compare to an implementation using closures?
Re:Building Fast Interpreters in Rust by YorickPeterse on Monday March 04, 2019 @12:31PM (#19303089)
Inko (https://inko-lang.org/) uses this approach for its bytecode interpreter. I can't speak about how well it performs compared to an AST interpreter since I never wrote one for Inko, but generally bytecode interpreters should be faster. I also think bytecode interpreters would be easier to optimise, as you're no longer directly tied to the AST of the input language.

If you're curious, you can find Inko's interpreter loop here: https://gitlab.com/inko-lang/inko/blob/master/vm/src/vm/mach...

Re:Building Fast Interpreters in Rust by RReverser on Monday March 04, 2019 @12:48PM (#19303323)
> as you're no longer directly tied to the AST of the input language

This is the case with closure-based approach too, and indeed was one of the main motivations to switch away from AST interpretation.

Re:Building Fast Interpreters in Rust by RReverser on Monday March 04, 2019 @11:52AM (#19302665)
It was even slightly worse than AST interpretation actually - as mentioned in the post, this is a much larger topic that deserves its own attention, but I did include the bytecode case in the linked slides. https://www.slideshare.net/RReverser/building-fast-interpret...
Re:Building Fast Interpreters in Rust by munificent on Monday March 04, 2019 @01:31PM (#19303823)
My understanding is that you're doing subroutine threaded code[0] instead of bytecode, which isn't a common choice. Maybe this has something to do with Rust's particular performance profile.

Did you profile your bytecode implementation to see where the time is going?

[0]: https://en.wikipedia.org/wiki/Threaded_code#Subroutine_threa...

Re:Building Fast Interpreters in Rust by skybrian on Monday March 04, 2019 @12:04PM (#19302774)
Well, that's disappointing. Any guesses about why?
Re:Building Fast Interpreters in Rust by RReverser on Monday March 04, 2019 @12:10PM (#19302827)
If you mean in a comparison against AST interpretation, then I'd say that Rust tagged enums (which C doesn't have) are already essentially a bytecode, except properly aligned, so it makes sense that they have same or slightly better performance than you would get with manual bytecode implementation.
Re:Building Fast Interpreters in Rust by ufo on Monday March 04, 2019 @02:02PM (#19304177)
I am not sure if I understand this argument. The C "tagged union pattern" is very similar memory-wise to Rust tagged enums, it just is less pleasant to use due to no pattern matching and so on.

I would expect that the advantage of the closure version of your AST interpreter is coming from somewhere else (at the end of the day it is still an AST interpreter). One possibility is that you are passing around the operands to your filters as Rust lexically-closed values instead of using a custom stack for your interpreter, which makes things a bit more "statically typed".

Re:Building Fast Interpreters in Rust by RReverser on Monday March 04, 2019 @08:21PM (#19307332)
> at the end of the day it is still an AST interpreter

In a pretty remote sense, I guess.

> One possibility is that you are passing around the operands to your filters as Rust lexically-closed values instead of using a custom stack for your interpreter, which makes things a bit more "statically typed".

Yes, that and using native dynamic dispatch instead of walking a tree structure with branching are making this technique much closer to template JITs than AST interpretation, with corresponding performance wins.

Re:Building Fast Interpreters in Rust by ufo on Monday March 04, 2019 @10:13PM (#19307811)
The reason I think it is fair to call it an AST interpreter is that there is roughly one function call for each AST node. This isn't necessarily a bad thing though -- AST interpreters are super simple and maintainable (as you have demonstrated) so if you got good performance out of one then it is great!
Re:Building Fast Interpreters in Rust by ridiculous_fish on Monday March 04, 2019 @01:14PM (#19303648)
C certainly has this via unions. It's more manual but provides more control, including choice of alignment.

It's CPU dependent but in practice the cache benefits of packed instructions will probably dominate any misalignment penalty. It's not like x86 instructions are aligned after all.

What's the best way to implement a variable-width bytecode interpreter in Rust?

Re:Building Fast Interpreters in Rust by RReverser on Monday March 04, 2019 @01:28PM (#19303797)
More control is, as usual, less ability for automatic optimisations. For example, Rust uses knowledge of tag + data combinations for niche-filling enums where possible (and more similar optimisations to come), while LLVM can use this information to more easily deduce which branches are unreachable.

As for alignment - you can control it in Rust enums too, if you wish, by putting custom aligned structures as variant payloads.

In general, I'm sure it would be possible to achieve same (or, who knows, maybe even slightly better) performance than AST interpretation with manual tweaking, but the closure-based approach proved to give significant wins out of the box, while being more elegant to maintain and extend over time.

Re:Building Fast Interpreters in Rust by ridiculous_fish on Monday March 04, 2019 @01:57PM (#19304122)
In a bytecode interpreter's loop, you want your language to be little more than a fancy assembler. Exotic techniques become viable and even critical for performance: computed goto, misaligned reads, pointer arithmetic, unchecked type conversions, etc. This is one place where you want to tightly control your machine code.
Re:Building Fast Interpreters in Rust by RReverser on Monday March 04, 2019 @02:21PM (#19304357)
Oh, sure, if you're willing to go to unsafe techniques, you can go straight to a template JIT as well. But, for us, the balance is important, and safety is probably even more important than further wins in performance (which we can still easily get with algorithm-level optimisations before doing anything unsafe).
Re:Building Fast Interpreters in Rust by pcwalton on Monday March 04, 2019 @04:45PM (#19305894)
Rust has C unions now, but without safety. I would probably write manual parsing code via the byteorder crate.
Re:Building Fast Interpreters in Rust by cqtoq on Tuesday March 05, 2019 @05:40AM (#19309651)
This is a bit off-topic, but it has been on my mind for a while.

Has the Rust team considered that choosing cute names like "crate" might turn away some of the target audience?

The same applies to other ecosystems like homebrew, but in the case of Rust some of the target audience certainly includes C/C++ programmers.

Re:Building Fast Interpreters in Rust by steveklabnik on Tuesday March 05, 2019 @06:50AM (#19309909)
C++ is itself a “cute” name. It seems to be doing just fine.

Beyond that, “crate” specifically has utility. The closest existing name is “compilation unit,” but with things like incremental compilation, that’s a bit of a misnomer. We need some kind of name.

Re:Building Fast Interpreters in Rust by fulafel on Thursday March 07, 2019 @05:01AM (#19326835)
C too (from B, which was a stripped down BCPL).
Re:Building Fast Interpreters in Rust by kstenerud on Monday March 04, 2019 @12:58PM (#19303474)
This is how ragel [1] does things. It basically compiles to giant-switch state machines, and is damn fast.

[1] https://www.colm.net/open-source/ragel/

Re:Building Fast Interpreters in Rust by nicoburns on Monday March 04, 2019 @10:10PM (#19307794)
Ragel was also the source of cloudbleed! So I would definitely understand if cloudflare were wary of using that kind of technique again.
Re:Building Fast Interpreters in Rust by jgrahamc on Tuesday March 05, 2019 @04:10AM (#19309237)
That's not the right way to talk about it. The error was in code that used Ragel but it was all on us. But as part of improving things we have moved away from non-memory safe languages and Ragel was using C.
Re:Building Fast Interpreters in Rust by nicoburns on Tuesday March 05, 2019 @06:45AM (#19309882)
Ah, I had thought it was a bug in the code generated by ragel. But I guess not!
Re:Building Fast Interpreters in Rust by jgrahamc on Tuesday March 05, 2019 @11:38AM (#19311731)
In a way it was, but it wasn't Ragel's fault. We made an error that ended up with generated C code that contained the bug. Bottom line for us was... use memory safe languages.
Re:Building Fast Interpreters in Rust by Matthias247 on Monday March 04, 2019 @10:27PM (#19307863)
As far as I remember not Ragel was the issue, but the code around it. If you use a Rust parser from inside broken C code which Leaks memory the same thing can happen.
Re:Building Fast Interpreters in Rust by jgrahamc on Monday March 04, 2019 @03:35PM (#19305116)
Agreed. We used to use Ragel for some tasks and it was fast.
Re:Building Fast Interpreters in Rust by saagarjha on Monday March 04, 2019 @12:41PM (#19303234)
A better way to get good performance is to thread your switch statement, which is hard to do explicitly in Rust last time I tried (maybe you could do this if you mark functions as inlinable?).
Re:Building Fast Interpreters in Rust by ufo on Monday March 04, 2019 @01:14PM (#19303646)
With current branch predictors threaded code might not make a big difference like it used to.

https://hal.inria.fr/hal-01100647/document

Re:Building Fast Interpreters in Rust by skybrian on Monday March 04, 2019 @12:44PM (#19303266)
What do you mean by "thread your switch statement"?
Re:Building Fast Interpreters in Rust by ridiculous_fish on Monday March 04, 2019 @01:27PM (#19303780)
The "big switch statement" approach is for each bytecode instruction to complete by jumping to a centralized dispatch location (i.e. the switch statement).

The "threaded" approach is for each bytecode instruction to complete by decoding and jumping to the handler for the next instruction.

Basically instead of "break" you have `goto handlers[nextIp->opcode].`

The advantages of threading are fewer jumps and better branch prediction (since branch prediction is tied to IP). The disadvantages are slightly larger code and compilers struggle to optimize it, since the control flow is not structured.

Re:Building Fast Interpreters in Rust by filereaper on Monday March 04, 2019 @06:55PM (#19306890)
This method of design is called a Continuation Passing Style Interpreter. [1]

Here's a production version from OpenJ9's JVM ByteCode Interpreter. [2]

[1] https://kseo.github.io/posts/2017-01-09-continuation-passing...

[2] https://github.com/eclipse/openj9/blob/01be53f659a8190959c16...

Re:Building Fast Interpreters in Rust by anisppp on Monday March 04, 2019 @12:57PM (#19303457)
Re:Building Fast Interpreters in Rust by smuser on Monday March 04, 2019 @12:58PM (#19303471)
I think he means have a different thread for each case or a subset of cases instead of having any explicit switch statement.
Re:Building Fast Interpreters in Rust by Thaxll on Monday March 04, 2019 @02:59PM (#19304745)
Looks like what emulator are using.