Skip to content

Instantly share code, notes, and snippets.

@robertknight
Created August 21, 2026 15:17
Show Gist options
  • Select an option

  • Save robertknight/f5ea22933579f6c9f1389a2bd2b89305 to your computer and use it in GitHub Desktop.

Select an option

Save robertknight/f5ea22933579f6c9f1389a2bd2b89305 to your computer and use it in GitHub Desktop.
Impact of RustFFT on RTen build times

The fft.rs finding in detail

How I found it

In the split build, rten-ops-misc2 (control_flow, fft, non_max_suppression, random — about 1,400 non-test lines) was the last crate to finish and defined the critical path. That was out of proportion to its size, so I bisected it by feature:

build of rten-ops-misc2 wall CPU
--features fft,random 4.20s 8.93s
--features random 0.50s 1.83s
no features 0.49s 1.72s

So fft.rs — 436 non-test lines — accounts for 3.7s wall / 7.1s CPU on its own.

Isolated reproduction

A standalone crate whose entire source is this:

pub fn plan(len: usize) -> Arc<dyn Fft<f32>> {
    FftPlanner::<f32>::new().plan_fft_forward(len)
}
pub fn run(f: &dyn Fft<f32>, buf: &mut [Complex32]) { f.process(buf); }

takes 3.61s wall / 7.20s CPU to build in release, and emits 288,842 lines of LLVM IR across 3,386 functions. rten-ops-fft emits 311,246 — so 93% of the codegen in rten's FFT crate comes from that one planner call, not from rten's code.

cargo llvm-lines attribution for rten-ops-fft:

origin LLVM IR lines share
rustfft::* 274,508 88.2%
rten's own fft.rs 17,785 5.7%
everything else 18,953 6.1%

-Ztime-passes on the probe crate: 0.4s frontend, 0.2s codegen_to_LLVM_IR, 3.28s in the LLVM backend. It's not type-checking or trait resolution — it's LLVM optimizing a quarter-million lines of IR.

The mechanism

FftPlanner<T: FftNum> is generic over the scalar type, and its planning logic statically names every algorithm rustfft implements — radix-N butterflies, Bluestein, Rader, mixed-radix, and the SIMD variants. Monomorphization walks all of those reachable paths, so calling FftPlanner::<f32>::new() instantiates rustfft's entire algorithm zoo into your crate. rustfft's own prebuilt rlib (1.4s) contains almost none of this — it's all generic, so it lands downstream.

Two details make it worse than it needs to be:

  • 62% of the whole crate's IR is rustfft::neon::* (192,271 lines).
  • 64,166 of those lines are NeonF64Butterfly*<f32> — the f64-lane NEON butterflies instantiated with T = f32. The planner selects between f32 and f64 paths with a runtime type check, which mono collection can't fold away, so both branches get instantiated. The f64-with-f32 half can never execute; LLVM optimizes and emits it anyway.

Mitigations, measured

Same probe crate:

variant LLVM IR lines wall CPU
FftPlanner (default) 288,842 3.61s 7.20s
FftPlannerScalar 93,274 1.25s 3.21s
rustfft with default-features = false 93,352 1.30s 3.36s

And end-to-end on unsplit main + lto = "off", A/B interleaved, min of 3:

config time
main + lto="off" 14.71s
main + lto="off" + rustfft default-features = false 12.89s (−12.4%)

That's a one-line change to the workspace dependency worth more than my entire crate split.

What this means for the split

Isolating fft.rs into rten-ops-fft (depending only on rten-ops-base, not on the elemmath chain) doesn't reduce the work — it moves it. The crate now starts at t≈5.3 and runs to t≈12.0 in parallel with everything else, instead of sitting in the middle of a serial chain. That's the general shape of what crate splitting buys you here: better packing, not less work.

Three caveats:

  • Both mitigations cost FFT throughput at runtime — the NEON butterflies are the fast path. For rten that's probably an easy trade (STFT/DFT are rarely a model's bottleneck), but it is a real one, and I haven't benchmarked it.
  • fft isn't in rten's default features, so library users who don't opt in never pay this. It hits rten-cli and rten-examples because they enable all-ops.
  • The cleanest fix is upstream: if rustfft exposed a non-generic FftPlanner32, the instantiation would happen once inside rustfft's own rlib and every downstream crate would get it for free.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment