HN
Today

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

A Rust developer meticulously details how replacing a 16-byte enum value with a 64-bit tagged word supercharged their Plush language interpreter. This deep dive into low-bit tagging schemes, cache-friendliness, and surprising Rust compiler behavior yielded a 17% overall speedup. It's a masterclass in VM optimization that delights performance-minded engineers.

13
Score
6
Comments
#15
Highest Rank
16h
on Front Page
First Seen
Sep 8, 6:00 PM
Last Seen
Sep 9, 9:00 AM
Rank Over Time
17181615161920202323252628303028

The Lowdown

Maxime Chevalier-Boisvert, the author of the dynamically-typed Plush language, recounts a significant optimization effort focused on its core value representation. This post details the journey from a standard Rust enum to a custom 64-bit tagged word, revealing impressive performance gains and memory reductions.

  • The original Value type, a Rust tagged enum, occupied 16 bytes due to memory alignment, even though its components required less, leading to inefficient memory use.
  • To combat this, a custom 64-bit tagged word representation was designed, leveraging low-bit tagging techniques common in VM engineering.
  • This new scheme efficiently packs various value types: 62-bit fixnums (integers), self-tagged flonums (floats), immediates (like nil, true), and different pointer types.
  • Fixnums are stored as n << 2, allowing direct arithmetic operations on the tagged word and efficient overflow checks.
  • Flonums utilize a 'float self-tagging' method, subtly manipulating exponent bits to embed tag information without significant loss of precision or range.
  • Memory usage saw substantial improvements, with up to a 37% reduction for memory-intensive benchmarks; initial regressions in specific cases were addressable with minor code adjustments.
  • Surprisingly, all benchmarks showed performance gains, with some experiencing a 17% speedup, even for floating-point heavy workloads that required additional unboxing/re-boxing operations.
  • The primary reasons for the performance boost were identified as reduced memory traffic due to improved cache-friendliness, and the generation of significantly more efficient machine code compared to Rust's enum match statements, which often led to excessive register spills and memory accesses.

In conclusion, this refactoring proved highly successful, not only streamlining memory usage but also delivering a universal performance uplift across Plush benchmarks. The author notes that careful data representation, especially when values fit into a single register, can bypass inefficiencies in compiler-generated code, pushing Plush closer to its goal of real-time 3D graphics rendering.

The Gossip

Crafting Code for Coherence and Computers

Commenters initially debated the trade-off between the readability and maintainability of Rust's native enums versus the performance gains of low-level bit manipulation. The prevailing sentiment was that for core interpreter components, optimizing for machine efficiency is paramount. While the underlying bit operations are complex, the author's provision of clean, high-level abstractions for the new `Value` type ensures that most developers interacting with the interpreter won't need to delve into the intricate details.

Rust's Reach: Leveraging Unsafe for Untapped Speeds

The discussion also delved into how Rust accommodates such low-level optimizations. While Rust's stable features don't directly expose mechanisms for this advanced control over value representation, the community highlighted the effective use of 'unsafe' Rust. The suggested best practice involves isolating the complex, unsafe bit-level operations within a small, dedicated crate (potentially verified with Miri) to then expose a safe, high-performance `Value` type API to the rest of the application, thereby balancing performance with Rust's safety guarantees.