Exploring Rust And Openended Algorithms

Choose your reading experience:

Hey readers!

Here's a code example of Map-Elites in Rust:

Inspired by the success of packages like uv (written in Rust) and bun (crafted in Zig), I started wondering: if these new backends can give languages like JavaScript a performance boost, could a similar approach work for algorithms that have long relied on high-level implementations?

One algorithm that has always fascinated me is Map-Elites—a quality-diversity algorithm that not only explores a search space but also seeks to discover a diverse set of high-performing solutions across multiple dimensions. Given Rust's promise of low-level memory control and performance, I hypothesized that a native Rust implementation of Map-Elites might outperform a typical high-level version. For those interested in the foundational ideas behind Map-Elites, you can explore more in this arXiv paper.

Moreover, if you're curious about how these concepts extend to robotics, there's innovative work on robots that can adapt like animals. This research demonstrates how adaptive, resilient systems can emerge through open-ended algorithms. For a deeper dive into this realm, check out this related research work.


Why Rust for Map-Elites?

The idea was straightforward. Rust offers:

  • Direct memory interfacing: Which should, in theory, allow for tighter control over data layouts and better cache utilization.
  • Zero-cost abstractions: Enabling high-level programming without sacrificing low-level performance.
  • Safety guarantees: Helping me focus on optimization without worrying about undefined behaviors.

Armed with these advantages, I set out to implement Map-Elites in Rust, expecting it to give me that extra speed boost.


The Hypothesis

Before diving into the benchmarks, I assumed that a Rust implementation—thanks to its low-level control and direct memory access—would naturally outpace one built on high-level libraries like pandas. I thought that native code was inherently faster than its interpreted counterparts.

It wasn't until I ran the tests that I learned something new: Python's ecosystem, especially libraries like pandas, has been honed over decades. Leveraging optimizations such as BLAS (Basic Linear Algebra Subprograms) and SIMD (Single Instruction, Multiple Data) instructions, these tools pack a serious performance punch. This revelation completely upended my initial assumptions about native versus high-level performance.


Implementation in Rust

I set up a Rust project and dove into the Map-Elites algorithm. The implementation involved:

  • Efficient Data Structures: Custom structs and enums to represent the behavioral dimensions and the elite map.
  • Direct Memory Management: Leveraging Rust's ownership model to handle data without garbage collection overhead.
  • Parallel Processing: Using Rust's concurrency features to spread the workload across multiple cores.

I meticulously optimized the code, expecting that every microsecond saved at the memory level would add up to a significant advantage in speed.


Benchmarking Surprises

After implementing the algorithm, I ran a series of benchmarks comparing my Rust version against a version built using Python's pandas. And here's where the unexpected happened:

  • Pandas outperformed Rust!
    Despite the direct memory access and low-level control offered by Rust, the pandas version was significantly faster. The reason? Pandas is heavily optimized for numerical computations—it leverages BLAS and SIMD operations under the hood, which are hard to beat even with native code.

This was a humbling reminder that high-level abstractions, when backed by years of low-level optimization in well-established libraries, can sometimes outperform our hand-tuned implementations.


Lessons Learned

This experiment taught me several key lessons:

  1. Optimization Isn't Just About Low-Level Control:
    Even though Rust provides incredible control over memory and performance, leveraging mature libraries like pandas—optimized with decades of expertise in BLAS and SIMD—can yield superior performance.

  2. The Value of Abstraction Layers:
    High-level libraries are more than just convenience wrappers. They encapsulate a vast amount of optimization work that isn't trivial to replicate in a new implementation.

  3. Reevaluating Assumptions:
    Our assumptions about native implementations always being faster need constant reevaluation, especially in an era where libraries can harness the full power of modern hardware.

  4. Future Hybrid Approaches:
    This insight opens up avenues for exploring hybrid solutions where the ease of high-level libraries can be combined with targeted low-level optimizations when needed.


Moving Forward

While my initial hypothesis about Rust's potential to outpace a pandas-backed implementation didn't hold up in this case, the journey was invaluable. It has not only deepened my understanding of both Rust and high-level libraries but also highlighted the importance of leveraging the best tools available—regardless of their language of origin.

I'm excited to explore further hybrid architectures and see if there are other scenarios where native code can reclaim the performance crown. Have you ever had similar experiences where a high-level tool outperformed your native implementation? Let me know in the comments!

Until next time, keep exploring and questioning the status quo.

Rach