logoalt Hacker News

Replacing a Rust Enum with a 64-Bit Word Made My Interpreter 17% Faster

130 pointsby metrofunlast Saturday at 12:32 PM48 commentsview on HN

Comments

MindSpunktoday at 3:59 AM

I'm not convinced the performance benefits are entirely the result of the more compact object representation. It definitely would help, but looking at the code snippets the author provides for the add instruction there's an important structural change that would be making a huge difference.

The old, enum based value type used a single big match statement to dispatch between all possible type combinations. Their assembler output looks like the match gets compiled to something like a big stack of nested if statements.

The new code uses an explicit fast path check with a dispatch into a tagged 'cold' path when the common case isn't hit. The generated code is a single upfront branch for the fast path that exits immediately, with a dispatch into the slow path in a separate function.

This would be contributing significantly to the performance improvements. The old path requires taking several branches even on the hot path. The new code has a single, highly predictable branch that skips all the messy dispatch for the other types.

This could have been implemented for the enum based value type, and I would expect to see a jump in performance there too even without the new compact value type. There will be a much higher branch predictor hit rate with the explicit fast path.

show 2 replies
fpolingyesterday at 8:40 PM

The article title is misleading. It is not that Rust compiler was not able to optimize some low-level operations. Rather the author came up with encoding schema that fit most things the interpreter dealt with into 64 bit. This replaced the previous schema that used 128 bit for everything but that can be directly mapped into Rust enums. The catch was that it was necessary to allocate some things on the heap and use pointer indirection but that was used for rare values so on average the new schema provided nice win.

One cannot expect a compiler to come up with such encoding.

show 3 replies
lowbloodsugaryesterday at 5:59 PM

Take a look at triomphe's ArcUnion and extrapolate from there. Basically make a crate for just your 64bit union type, do it unsafe there, test with miri, and now you have a safe 64bit type you can use with match. You're happy digging around assembly so this is well within your wheelhouse. The only challenge will be if you do use miri to verify then you need to use the 'provenance-preserving' pointer adjusting functions. Worth the learning experience in my opinion. I did one for my system and it was super fun and had the performance impact you describe.

gigatexalyesterday at 5:53 PM

But isn’t the enum far more readable and maintainable than having to do bit operations on things?

show 6 replies
nwhiteheadyesterday at 9:50 PM

"the smart thing to do is to give integers zeros as their tag bits, because then, adding or subtracting two shifted integers remains a plain add or sub machine instruction"

this is brilliant, love it. stealing this idea immediately.

show 1 reply
Ozzie-Dtoday at 3:28 AM

[flagged]

krickyesterday at 8:05 PM

That's very unpleasant to hear. It's sad to be reminded that Rust compiler is not magic and cannot just... do these things somehow. Sure, all abstractions do have some cost, but, man, 17% performance gain by virtue of replacing enum with this monstrosity? That's very annoying.

show 3 replies