Skip to content

webasm (and cpu): scatter/predicate kernels run 20–32× slower than the plain JavaScript they were transpiled from #869

Description

@fuzzie360

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions