github twitter linkedin instagram
Rayon and High Performance Computing (HPC)
Aug 1, 2026
5 minutes read

Intro

In high performance computing (HPC), it’s important to know how your code is running. I’d argue a high performance mentality should be used in many more places than it is currently but anyways, on to the article!

AI non-use: I disabled my LLM tooling setup in neovim and did not feed this article through an LLM for editing. I’m not anti-LLM but I find writing a means to clarify my own thoughts.

TL;DR:

I’m working on a new audio production program (it’s called Fibbognocci for now) that I’m parallelelizing with rayon. I want the time between clicking “listen to audio” and the audio playing back to be as short as possible, so applying HPC techniuqes is an audio-production-ergonomics decision.

NotLongEnough;WantToReadMore (NLE;WTRM)

Project Context

… ok so what does “high performance mentailty” mean for my project? I have a boat load of arithmetic operations running through a CPU and numbers passing between memory strata (registers, cache layers, RAM). Basically how audio synthesis works is you start with a big chunk of zeroed out memory, then apply mathematically derived values to it where increasing memory addresses correlate with an increase in time. For the purposes of this article, I’m going to use simplified code snippets compared to what I have in the actual codebase.

For example if you want to generate a 261.63Hz sine wave (aka middle C), a function would look something like this:

fn generate_sine_wave(
    frequency: f64,
    sample_rate: u32,
    sample_count: usize
) -> Vec<f64> {
    let phi = (2.0 * PI * frequency) / sample_rate as f64;

    (0..sample_count)
        .map(|i| {
            let t = i as f64 / sample_rate as f64;
            let phase = phi * t;
            phase.sin()
        })
        .collect()
}

In my actual project I’m using some fancier math to support things like smooth frequency transitions, but the above is a very simple example for how to generate a nice sounding sine wave. Effectively all off my synthesizer code is written like the above function - iterate over some indices or a chunk of memory, and apply some math ops to it. My input data is going to be in a plain text MIDI-like file, but for now I’m writing the audio data AST directly.

With all that in mind, I don’t care about IO performance NEARLY as much as CPU and memory operation performance. If you’ve read the book Algorithmica or are otherwise computer architecture educated, you’ll know contiguous memory accesses and short, repetitive branchless CPU operation sequences are EXTREMELY performant on modern CPUs.

Parallelism in Fibbognocci

For composition, I’m effectively calling generate_sine_wave a bunch of times, adding them together, then applying Dynamic Range Compression (DRC) them to normalize with a (-1.0, 1.0) scale amplitude scale. This is a very very VERY very super duper parallelizable problem! Without considering Rust libraries, I could create a thread pool to generate each sample sequence, sum them all up, then apply DRC to get the normalized PCM data.

Now the question is, what frameworks would help me with this?

Tokio

The first framework that came to mind was Tokio because that’s what I’ve been using for the past 4ish years at Searchless and Dropbox, aside from implementing thread pools in std Rust in another experimental project I worked on a while ago (b64, a B compiler).

For anything IO bound Tokio is great, but I quickly realized that for raw number crunching and data schlepping, Tokio doesn’t really help much in writing optimized code. Sure you could create a thread pool and use non-async code everywhere to avoid undesired branching, but that’s basically the same as rolling a thread pool with built in concurrency primitives.

Rayon

I like to consider Rayon as the parallelism-and-pipelining-first analogue to Tokio. Where Tokio helps you with async IO, Rayon makes optizing non-IO-bound parallizable jobs much much much easier.

fn generate_sine_wave(
    frequency: f64,
    sample_rate: u32,
    sample_count: usize
) -> Vec<f64> {
    let phi = (2.0 * PI * frequency) / sample_rate as f64;

    (0..sample_count)
        .into_par_iter() // Rayon-ify
        .map(|i| {
            let t = i as f64 / sample_rate as f64;
            let phase = phi * t;
            phase.sin()
        })
        .collect()
}

Here’s an example of how you could rayon-ify the previous code snippet. A single line of code and BOOM the generate_sine_wave function is gonna crank through this task.

What compute environment makes most sense?

“Where and how is my code running?” is an extremely important question to ask yourself in HPC. For Fibbognocci, it’s going to be running on a CPU. Duh. So why, you might be asking yourself, did I point out something so obvious? I’m glad you asked dear reader! Because I’m going to talk about cloud computing!

Say I wanted to host a service that generates audio in your web browser and I want to keep all audio synthesis code on the server. I like the convenience of cloud computing, but it’s important to know what cloud services to use for a task like this. For the absolute best performance I would want to run this on bare metal - but in practice I wouldn’t do that. Instead, I would rent a reserved compute instance, i.e. no containerizing stuff on top, because I want to avoid context switching as much as possible to help the CPU as much as I can. As such, I would probably have a dedicated single-threaded process for the web server using tokio, with all other threads assigned to a side car program which does all the interesting work.

Another topology I could see working here is two separate services - a web server running on a shared instance in a container, and a separate service running without containerization running directly on the OS (or an FPGA instance if I really wanted to optimize the hell out of this).

Conclusion

Enough writing in English for me.. I’m going back to writing in my native language now! (crab language of course)


Back to posts