I haven't really looked into string interpolation perf - do you know offhand what's inefficient about it at the moment?
I'm doing perf stuff now but plan to stop that in favor of bugfixes only pretty soon, so now's the time for fixes like that :smile:
Richard Feldman said:
I haven't really looked into string interpolation perf - do you know offhand what's inefficient about it at the moment?
Its all mentioned here https://github.com/roc-lang/roc/issues/12824:
2 have PRs (#12750 + #12823) that are changes to built-ins only.
Those two alone close the gap:
┌─────────────────────────────────┬────────────────────────┬──────────────────┐
│ competitor │ instructions a request │ cycles a request │
├─────────────────────────────────┼────────────────────────┼──────────────────┤
│ fourneau-zig │ 344k │ 216k │
├─────────────────────────────────┼────────────────────────┼──────────────────┤
│ roux, nightly + #12750 + #12823 │ 502k │ 383k │
├─────────────────────────────────┼────────────────────────┼──────────────────┤
│ axum │ 510k │ 410k │
├─────────────────────────────────┼────────────────────────┼──────────────────┤
│ roux, nightly-2026-10-24 │ 687k │ 511k │
├─────────────────────────────────┼────────────────────────┼──────────────────┤
│ go (templ) │ 2,231k │ 1,889k │
├─────────────────────────────────┼────────────────────────┼──────────────────┤
│ basic-webserver │ 3,640k │ 3,924k │
└─────────────────────────────────┴────────────────────────┴──────────────────┘
The last is doing something more complicated for string interpolation.
Richard Feldman said:
I haven't really looked into string interpolation perf - do you know offhand what's inefficient about it at the moment?
Sorry I didn't remember that i already cross posted that summary....
specifically the string interpolation compilers to a List(Str) doing allocation and holding a closure. against a handwritten chain of concats difference was 37% in cpu cycle, but i did not disambiguate how much of that comes just from the optimizing away zero length strings in the interpolation string.
turns out, it was 100% the empty string optimization.
For the other 2 PRs, I'm currently doing some perf regression testing on how it affects largers strings.
https://github.com/roc-lang/roc/pull/12830
There's a proof of concept for a slight improvement. It implicitly eliminates empty strings and that's the majority of improvement.
I think the other two have more bang for the buck
hrm, this investigation makes me realize that from_interpolation should take an Iter, not a List, now that Iter exists
because it doesn't require a heap allocation the way List does
@Zak Kohler thanks for opening all this! I'm gonna merge your other two PRs and then open a replacement PR for https://github.com/roc-lang/roc/pull/12823 that makes the API change, along with the change you have in there, and some other related changes
Updated the small str concat: https://github.com/roc-lang/roc/pull/12823 not sure which version you saw, but early experiments has some small regression in long string perf. This final version seems best of both worlds.
I had the agents summarize all the experi toments for the small-str concat optimization and update the PR message body.
Be sure check out the comparison it is quite interesting. I definitely am request some keen review on this as well since I'm steering the LLMs beyond what I fully grok here.
Finally, the small string in register perf is sensitive to inlining. Do you have any thoughts on perf regression testing/codegen asserts?
yeah https://github.com/roc-lang/roc/pull/12823#pullrequestreview-5763068602 is the one I had already approved. Re-approved!
Zak Kohler said:
Finally, the small string in register perf is sensitive to inlining. Do you have any thoughts on perf regression testing/codegen asserts?
can you say more about this? I need more specifics to understand the question! :smile:
Basically we are right at threshold of llvm's inlining discretion. Had to fiddle/measure to get out just right.
LLM summary:
Sure! The fast path in #12823 only pays off when LLVM inlines roc_builtins_str_concat at the call site. That keeps the small strings' words in registers across a chain of concats (about 15 cycles a concat, against about 90 on main). Inlining is LLVM's cost-model call, and the final wrapper is close to the line: at 74 IR instructions it's inlined at 44 of 44 sites on my test page, while an earlier 88-instruction version was inlined at only 16 of 43. So an innocent edit to the wrapper, an LLVM upgrade, or a change in how codegen calls it could quietly drop it out of line. Strings would still be correct, and it would still beat main (variants inlined nowhere measured −13% on the page against −30%), but most of the win would be gone and nothing would fail.
So the question is whether you'd want either:
a test that builds a small app at --opt=speed and asserts the optimized IR has no call @roc_builtins_str_concat in the hot function (the IR tests in MonoLlvmCodeGen.zig check pre-optimization IR, so this would need a post-opt hook), or
codegen marking that builtin alwaysinline, so it holds by construction, with the trade-off you already cap in #11451 if the wrapper ever grows.
https://github.com/roc-lang/roc/pull/12823#issuecomment-6622877160
Well your iter interpolation made the small-str concat superflous for at least my bench. I'm going to see if I could get the same small-str trick on top of the new interpolation mechanism.
Zak Kohler said:
. I'm going to see if I could get the same small-str trick on top of the new interpolation mechanism
A: nope
Its not a bad nope either, your implementation already lets it all happen naturally.
![]()
I expect the three string PRs to put us back above Rust/Axum based on cycle counts. I'm excited for the next Nightly! I'll call it now 12.4k req/s
A tangent:
The new harder template bench makes Go-lang look even weaker.
I decided to make it a more fair and switch from the std-lib reflection based templates to the fairly popular third party compiled templates (.templ)...
...just benchmarked it...it only boosts Go from 600->2500 req/s
Could someone move this to performance instead of show and tell?
Should I move all messages there or starting from a specific message?
I already split the tangent and renamed it, so this whole thread can move. i just couldn't move it to differnent area
This topic was moved here from #show and tell > Rocstache: small string concat perf by Anton.
Last updated: Nov 04 2026 at 18:54 UTC