bench: mono-dispatch 48x->15x after the devirt inline cache (#238)
The per-site inline cache (#237) resolves a statically-proven monomorphic devirt once instead of per call, so mono-dispatch is no longer worse than megamorphic. The remaining dispatch lever is the megamorphic case (a runtime receiver-type-keyed cache). Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
a31c1af8c4
commit
dcfa607dc2
1 changed files with 12 additions and 10 deletions
|
|
@ -45,25 +45,27 @@ regenerate locally), ascending:
|
||||||
| benchmark | ratio | axis |
|
| benchmark | ratio | axis |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `fib` | ~0.6× | call + integer arith |
|
| `fib` | ~0.6× | call + integer arith |
|
||||||
| `collections` | ~4× | persistent map/vector churn |
|
| `collections` | ~3.5× | persistent map/vector churn |
|
||||||
| `mandelbrot` | ~7.5× | pure float compute |
|
| `mandelbrot` | ~7.5× | pure float compute |
|
||||||
| `binary-trees` | ~10× | escaping short-lived records (allocation/GC) |
|
| `binary-trees` | ~10× | escaping short-lived records (allocation/GC) |
|
||||||
| `dispatch` | ~12× | megamorphic protocol dispatch |
|
| `dispatch` | ~12× | megamorphic protocol dispatch |
|
||||||
| `mono-dispatch` | ~48× | monomorphic protocol dispatch |
|
| `mono-dispatch` | ~15× | monomorphic protocol dispatch |
|
||||||
|
|
||||||
- **Compute (~0.6–7.5×)** is the substrate floor: Chez is a native-compiling AOT
|
- **Compute (~0.6–7.5×)** is the substrate floor: Chez is a native-compiling AOT
|
||||||
Scheme, not a profiling JIT. With native arith + direct-linking + inlining jolt
|
Scheme, not a profiling JIT. With native arith + direct-linking + inlining jolt
|
||||||
is at parity here — `fib` runs *faster* than JVM Clojure (no JIT warmup over a
|
is at parity here — `fib` runs *faster* than JVM Clojure (no JIT warmup over a
|
||||||
short run), `collections` is within ~4×, and `mandelbrot` (~7.5×) is the
|
short run), `collections` is within ~3.5×, and `mandelbrot` (~7.5×) is the
|
||||||
pure-tight-loop float ceiling that only native codegen moves further.
|
pure-tight-loop float ceiling that only native codegen moves further.
|
||||||
- **Dispatch & allocation (~10–48×)** are the remaining architectural gaps, though
|
- **Dispatch & allocation (~10–15×)** are the remaining architectural gaps, though
|
||||||
the type-proving / native-record / bare-field-read work has collapsed them by an
|
the type-proving / native-record / bare-field-read work has collapsed them by an
|
||||||
order of magnitude (`binary-trees` ~140×→~10×). jolt still does a full protocol-
|
order of magnitude (`binary-trees` ~140×→~10×, `mono-dispatch` ~330×→~15×). On a
|
||||||
registry lookup on every call; the JVM inline-caches a runtime-monomorphic site
|
*statically proven* monomorphic receiver — which whole-program inference now gives
|
||||||
to near-free — which is why `mono-dispatch` is *worse* than megamorphic and is now
|
for a record iterated out of a vector — devirt resolves the impl and a per-site
|
||||||
the standout gap. devirt fires only on a *statically proven* receiver; whole-
|
inline cache holds it (resolved once, not per call), so `mono-dispatch` is no
|
||||||
program inference now proves more of them, but a value iterated out of a vector
|
longer worse than megamorphic. The remaining lever is `dispatch`: a *megamorphic*
|
||||||
still needs one — a call-site inline cache is the missing lever. `binary-trees`
|
site has no static type, so it pays a full protocol-registry lookup every call
|
||||||
|
where the JVM uses a polymorphic inline cache — a runtime (receiver-type-keyed)
|
||||||
|
cache is the missing piece. `binary-trees`
|
||||||
nodes escape into the tree, so scalar-replace can't remove them — residual GC
|
nodes escape into the tree, so scalar-replace can't remove them — residual GC
|
||||||
pressure.
|
pressure.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue