Follows #866. Two workloads compile to WebAssembly successfully and produce correct results, but land far below the plain-JS loop they are a translation of. The cpu backend is equally slow on the same kernels, so this looks upstream of the wasm emitter rather than caused by it.
Apple M1 Max, Chrome 150, branch build of #866 (11b4c0e). Medians, warm.
| workload |
plain JS |
webasm |
cpu backend |
WebGL2 |
stream compaction — 2^22 fp32, ~50% kept, ×24 rounds |
585 ms |
12,501 ms (0.05×) |
13,016 ms |
410 ms |
| orientation histogram — 2048×2048 samples → 64 bins |
158 ms |
5,134 ms (0.03×) |
5,949 ms |
23 ms |
Both are correct — checksums match the plain-JS reference to within 1e-4 relative. It is purely throughput.
For contrast, on the same machine and the same harness, matmul goes the other way: 1400 ms plain JS → 857 ms webasm (1.63×), and 3106 ms on cpu, so webasm is 3.6× the cpu backend there. The emitter is clearly capable. Something about these two shapes costs ~20–30× rather than ~1×.
What the two have in common, and what a dense kernel like matmul does not:
- A predicate and a running offset per cell. Compaction's kernel evaluates a keep/drop test and then indexes by a prefix offset — a data-dependent write position rather than a static one.
- Many-to-one accumulation into a small output. The histogram's 64 bins are each read by a huge number of source samples, so the kernel loops over a large stride per output cell.
Both therefore have a per-cell loop whose trip count depends on data, and both index with computed integers. Guesses at what could produce a 20× factor, offered only as starting points since I have not read the emitter closely: integer index expressions being materialised as f32 and round-tripped per access; the divergent-lane masking described in the PR executing every lane's branch on kernels where the branch is the entire body; or the per-cell loop failing to hoist an invariant the JIT hoists trivially in the plain-JS version.
The cpu backend showing the same slowdown (1.04× and 1.16× of webasm respectively) is the strongest hint — whatever it is, it likely lives in the shared transpilation of these constructs rather than in the wasm-specific path. That also means fixing it would improve the cpu backend at the same time.
Reproduction: both kernels are in the gpu.rocks benchmark suite (src/Bench/workloads/compaction.js, histogram.js), each with a plain-JS oracle in the same file to diff against. Happy to reduce either to a standalone case if that would help.
Follows #866. Two workloads compile to WebAssembly successfully and produce correct results, but land far below the plain-JS loop they are a translation of. The cpu backend is equally slow on the same kernels, so this looks upstream of the wasm emitter rather than caused by it.
Apple M1 Max, Chrome 150, branch build of #866 (
11b4c0e). Medians, warm.2^22fp32, ~50% kept, ×24 roundsBoth are correct — checksums match the plain-JS reference to within 1e-4 relative. It is purely throughput.
For contrast, on the same machine and the same harness,
matmulgoes the other way: 1400 ms plain JS → 857 ms webasm (1.63×), and 3106 ms on cpu, so webasm is 3.6× the cpu backend there. The emitter is clearly capable. Something about these two shapes costs ~20–30× rather than ~1×.What the two have in common, and what a dense kernel like matmul does not:
Both therefore have a per-cell loop whose trip count depends on data, and both index with computed integers. Guesses at what could produce a 20× factor, offered only as starting points since I have not read the emitter closely: integer index expressions being materialised as f32 and round-tripped per access; the divergent-lane masking described in the PR executing every lane's branch on kernels where the branch is the entire body; or the per-cell loop failing to hoist an invariant the JIT hoists trivially in the plain-JS version.
The cpu backend showing the same slowdown (1.04× and 1.16× of webasm respectively) is the strongest hint — whatever it is, it likely lives in the shared transpilation of these constructs rather than in the wasm-specific path. That also means fixing it would improve the cpu backend at the same time.
Reproduction: both kernels are in the gpu.rocks benchmark suite (
src/Bench/workloads/compaction.js,histogram.js), each with a plain-JS oracle in the same file to diff against. Happy to reduce either to a standalone case if that would help.