Skip to main content

Why fusion

what fusion buys

Before the how, the what. This is what fusion buys, measured — every bar a JMH sweep you can hover, every card the exact @Benchmark methods behind it, so you can check each rival runs its best idiom rather than a strawman. The rest of this section is how it is done.

The headline: fourteen stages, warm

The pipeline from the introduction: flatMap, filter, map, zip, zipWithIndex, foldLeft (a seeded reduce), twice over. Every collection runs the identical code; the FArray entry differs by the one word:

loading source…
measuring…
Swept from empty to 100k elements. At 100k: ~36× the quickest rival, ~83× List, and ~15× the same pipeline run eagerly on FArray itself (the gap between compiled-once ops and a compiled pipeline). The lazy List.view/Vector.view forms don't rescue the collections on a chain this flatMap- and zip-heavy — their per-stage View wrappers cost more than the intermediates they remove, landing ~60–70× behind .fuse. Hover any column for exact ratios; the fused variant's allocation profile measures at inputs-only (4·N + 40 bytes per invocation, the benchmark's own zip source), with the pipeline contributing zero.

Against java.util.stream: the same pipeline, both fused

java.util.stream is the JVM's own fused pipeline, which makes it the fair rival for .fuse and the wrong one for the eager chains on the chains page (comparing a lazy pipeline against eager materialization would be a strawman, so it sits this one out there). The standard collections have their own lazy single-pass form too — List.view and Vector.view — so these charts include them as well; they too belong here rather than on the eager page. Here is the classic map → filter → foldLeft with every side collapsed to a single pass:

measuring…

Cold: no warmup lap

Because the specialization is in the bytecode, there is no lap where a boxing collection is briefly ahead:

measuring…

Streaming: constant memory without the streaming tax

The same fused loop runs over data too big for the heap — a generated sequence, or a file's lines, folded without holding either in memory, at no per-element streaming tax. The chart, against Iterator, LazyList, fs2.Stream and a hand-rolled BufferedReader, lives with the source model that makes it work.

A second pipeline, a different stage mix

A nine-stage chain built from a different set of combinators — map, filter, collect (a PartialFunction the macro folds into one filter-plus-map), zipWithIndex, takeWhile, and sum — so the win isn't an artifact of one lucky shape. Fused, eager, and the whole field, Int beside String:

measuring…

JSON projection queries

The byte-source numbers live with their scanners and rivals on the JSON integration page. The headline: 1.6× past jsoniter-scala's hand-written reader loop at ~1 B/op, 4–9× past the AST parsers, and 2.5× on selective queries where records are abandoned mid-scan.