Repository navigation
perf(wasm): tune inlining with link-time optimization - #135
Conversation
|
Thanks for this. I rebuilt the binary from the pinned The perf result doesn't reproduce on x86_64 Linux though (Node 22.21.1, isolated processes, trimmed mean of 7 batches, test/samples inputs). I also split the two flags to see which one is responsible:
angular / d3 / rollup unminified were -1% to -3%. So Suggestions:
Minor: with |
f45d1a0 to
07b764e
Compare
guybedford
left a comment
There was a problem hiding this comment.
Thanks for this, and for the careful measurements. I rebuilt the binary with the pinned toolchain (build-wasm-local, wasi-sdk 21 / binaryen 116) and it is byte-identical to the committed lib/lexer.wasm, and the full suite passes in all three modes.
I did an interleaved A/B of flag variants on the same src/lexer.js, Node 22.21 / V8 12.4, Linux x86_64. Medians over 60 shuffled rounds after warmup, all after wasm-opt -Oz:
| Build | Size | Large samples | Medium (~3 KB) | Small (~150 B) |
|---|---|---|---|---|
-Oz (control) |
22401 | 0% | 0% | 0% |
-Oz -flto -mllvm -inline-threshold=150 (this PR) |
25065 | -2% | -5.5% | -7.6% |
-Oz -mllvm -inline-threshold=150 (no LTO) |
25522 | -1 to -2% | ||
-Oz -flto only |
22057 | within noise | ||
-O2 |
27121 | -12 to -14% | -10% | -12% |
-O3 |
27894 | -13% | -10% | -14% |
Cold start (import + init + first parse in fresh processes) was within noise across all of them, about 0.1 ms spread.
Two things fall out of this:
-fltoon its own does nothing here, which is expected for a single translation unit. The whole effect comes from the inline threshold.-mllvm -inline-thresholdis an internal LLVM option without a stability guarantee across clang versions, while plain-O2is a stable public flag and gave roughly twice the speedup for another ~2 KB.
So the real decision seems to be how much inlined binary size the loader is willing to pay, and if some is acceptable, -O2 looks like the better point on that curve. Could you run -O2 through your arm64 harness alongside the current flags? If it holds up there too, I'd prefer that over the LTO / threshold pair.
Separately, the stack is based on 2.3.0 and the src/lexer.js change in #134 conflicts with #131 on main, so both will need a rebase before landing.
| # These are project-specific and are expected to be kept intact | ||
| WASM_TARGET := -target wasm32-unknown-wasi | ||
| WASM_EXTRA_CFLAGS := -I include-wasm/ -Wno-logical-op-parentheses -Wno-parentheses -Oz | ||
| WASM_EXTRA_CFLAGS += -flto -mllvm -inline-threshold=150 |
There was a problem hiding this comment.
On a single translation unit -flto had no measurable effect on its own in my runs (see the table in the review body); the gain is entirely from the inline threshold. If the threshold approach stays, this could be dropped along with the -Wl,--mllvm duplicate below, but see the review for the -O2 comparison.
There was a problem hiding this comment.
flto reduces the byte size without changing performance, that is why I would keep it.
555cd7f to
7cabdf3
Compare
|
Seems like there is a big difference between the platforms. O2 was significantly worse in my local tests for actual modules such as angular. |
guybedford
left a comment
There was a problem hiding this comment.
Rebuilt from the pinned toolchain again after the rebase and the binary is byte-identical, suite passes in all three modes. On x86_64 Linux this is now a small win across all samples with no regressions, so landing. We can revisit the optimization level separately with arm64 numbers alongside.
Link-time optimization and selective inlining reduce mean warm parse time by 8.2%, 6.7%, and 10.3% on `magic-string.min.js`, `rollup.min.js`, and `angular.js`. The binary grows from 22,752 to 25,401 bytes. Measurements use Node 22.23.4/V8 12.4 on macOS arm64. Each workload ran in five fresh processes. Each process warmed with 50,000 calls and timed seven batches. The benchmark drops the fastest and slowest batch per process, then averages the middle five batch means and the five process means. The default and LTO builds return identical parse results on all three samples. On `magic-string.min.js`, median cold import, initialization, and first parse take 3.950 ms with default flags. The LTO build takes 3.986 ms across seven fresh processes. Signed-off-by: Ruben Bridgewater <[email protected]>
7cabdf3 to
6f58e7b
Compare
|
For flto: it is actually about size. It shrinks the size by a few hundred bytes. |
Link-time optimization and selective inlining reduce median warm parsing CPU by 12.7%, 8.8%, and 12.5% for small, medium, and large application modules. The control uses default flags. The binary grows from 22,639 to 25,277 bytes.
Measurements use Node 22.23.4/V8 12.4 on macOS arm64 and five fresh processes. Each process has 50,000 warmup calls per variant and seven interleaved batches with trimmed CPU means. Median cold import, initialization, and first-file parsing measures 1.851 ms with default flags and 1.963 ms with this build.