Open and answer this issue on GitHub.
performance, ready · opened 2026-10-12 · updated 2026-10-12 · 0 comments
An outside proposal (generated by Gemini) suggested four kinds of “pipeline fusion” so that Nikaia reaches Mojo’s speed:
map/filter chains into one loop;Its premises were partly wrong. It assumed an arena that is built (ADR-327 decides one, but it is not built, see #490), and it claimed that && is “one decision point”. The owner asked what can be taken from it. The goal: Nikaia should compete with Mojo where the plain chain a model writes is as fast as the loop an expert tunes, with every check kept. Mojo gets its speed from written SIMD and vectorize, and does not check overflow.
xs.iter().map(…).filter(…) lowers to Rust’s iterator adapters (ADR-293 D20). LLVM fuses those, so a separate fusion pass would add nothing there.map is eager. xs.map fn(x) { x * 2 }.map fn(y) { y + 1 } lowers to xs.map(|x| …).map(|y| …) on nikaia_std::list::ListExt, which is into_iter().map(f).collect(). That is two passes. Rust reuses the buffer where the element sizes match; elsewhere a new list is built. Under ADR-327 the list between the steps takes room in the task’s block.filter. It is xs.iter().filter. Part III’s paragraph on Seq said xs.map fn … hands back a sequence, which contradicts Part I 5.3 and #517. It now says xs.iter().map fn ….a && b stays two branches after inlining, because of the short circuit. One branch needs &, which is only allowed where the second condition cannot panic.filter after map over a sequence fails in rustc.ADR-342, with the rule in Part I 4.5 and Part III A.2:
map whose result goes straight into the next step runs element by element with it, where every lambda touches nothing and changes no name outside it. A chain with I/O or a lock runs as written.&, or the filter may run without a branch.map whose result is not named and goes into another map, iter(), a for or len() is lowered as into_iter().map(f).map(g)…, with one collect where a list is wanted. The condition is the one par_iter lambdas meet (touches empty, no NK2107), without the demand not to pause.
xs.map(f).map(g) gives the same output as today.printlns keeps today’s order: all of f, then all of g.map/sum over i64, three ways:
overflowing_* with the flags OR-ed and one check after the loop;Then the same chain where LLVM does not vectorize. Emit D3 only where it is not slower. A value that overflowed must reach no index, division or condition before the check.
src/main.nika:N: … attempt to add with overflow, naming the line of the +.&& (today);&;Emit each form only where it is not slower.
&&.filter as a method of a list.par_iter.