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:
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:
Cold: no warmup lap
Because the specialization is in the bytecode, there is no lap where a boxing collection is briefly ahead:
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:
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.