[ 
https://issues.apache.org/jira/browse/GROOVY-12284?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18106841#comment-18106841
 ] 

ASF GitHub Bot commented on GROOVY-12284:
-----------------------------------------

daniellansun commented on PR #2822:
URL: https://github.com/apache/groovy/pull/2822#issuecomment-5377696003

   # GROOVY-12284 Performance Verification Report
   
   **Specialize indy `sameClasses` guards for arity 1–4**
   (after PR #2822 review follow-up: `insertArguments` + `changeReturnType`)
   
   | Item | Value |
   |---|---|
   | Issue | [GROOVY-12284](https://issues.apache.org/jira/browse/GROOVY-12284) 
|
   | PR | [#2822](https://github.com/apache/groovy/pull/2822) |
   | Tree under test | GROOVY-12284 working tree (`47bd7b07` + review 
follow-up: `insertArguments`, `changeReturnType(boolean.class)`, no `n==0` 
combinator) |
   | Baseline | `e801df39580ab480249e561e90cf661ce86917f5` |
   | Date | 2026-08-22 |
   | Core tests | `./gradlew :test` — **16949 passed / 60 skipped / 0 failed** |
   | Verdict | **PASS** — specialised guards remain 1.5–1.9× (arity 1–3) and 
**4.68×** (arity 4) vs the collector parent; `insertArguments` vs chained 
`bindTo` is **0.99×** (noise); arity 5 / `@CompileStatic` stay at parity |
   
   ---
   
   ## 1. Executive summary
   
   The live call-site helper now binds expected classes with **one**
   `MethodHandles.insertArguments` against `SAME_CLASS_GUARDS[n]`, and takes
   the invocation `MethodType` so the guard signature is
   `callType.changeReturnType(boolean.class)`. That is the shape Jochen and
   Paul asked for on #2822. It does not change the user-visible mechanism:
   arity 1–4 avoid `asCollector`; arity ≥ 5 still collects.
   
   Order-balanced A/B against `e801df39` (same host, JDK, JMH annotations as
   the pre-review run):
   
   | Row | Geomean HEAD / parent |
   |---|---|
   | `dynamic_arity1` | **1.54×** |
   | `dynamic_arity2` | **1.86×** |
   | `dynamic_arity3` | **1.93×** |
   | `dynamic_arity4` | **4.68×** |
   | `dynamic_arity5` (collector both sides) | 1.11× |
   | `cs_arity2` / `cs_arity5` | **1.01× / 0.96×** |
   | Isolated combinator, insert vs collector (arity 1 / 2 / 4) | 1.15× / 1.19× 
/ 1.29× |
   | Isolated combinator, insert vs chained `bindTo` (arity 4) | **0.99×** |
   | `cpuIntegerOps` ruler | **1.00×** |
   
   GC fingerprint is unchanged: parent arity 4 = **32 B/op**, HEAD ≈ 0;
   arity 5 = **40 B/op** on both sides.
   
   `insertArguments` is the right production shape (one adapter, same JIT
   result as four `bindTo`s). The end-to-end win is the specialised leaf
   versus `asCollector`, not bind-vs-insert.
   
   **Performance verification: PASS.**
   
   ---
   
   ## 2. What changed since `47bd7b07`
   
   Review consensus (Jochen Theodorou, Paul King):
   
   1. Table of specialised handles + **one** `insertArguments` (not chained 
`bindTo`).
   2. Guard signature from `handle.type().changeReturnType(boolean.class)`.
   3. Drop the unreachable `n == 0` constant-`true` combinator.
   4. Fix MH-bench javadoc (`@Setup` fails pre-12284; the class still compiles).
   5. Keep the JMH benches.
   
   Out of scope, as Jochen marked it: `ClassValue` cache of forms by call-site 
index.
   
   ---
   
   ## 3. Methodology
   
   | Item | Value |
   |---|---|
   | OS / host | Linux 6.15.5, `hera` |
   | CPU | AMD EPYC 7763, 6 vCPUs (KVM) |
   | JDK | Amazon Corretto **25.0.2** |
   | JMH | 1.37; `@Warmup(3×2s) @Measurement(5×2s) @Fork(2)` |
   | Parent | worktree `/tmp/groovy-12284-parent` @ `e801df39` |
   | Bench parity | `SameClassesGuardBench.groovy` identical in both trees |
   | Order | T1 HEAD then parent; T2 parent then HEAD |
   | Suites | indy A/B, GC profiler, `SameClassesGuardMhBench` (HEAD), CPU 
rulers |
   
   Throughput ratio = HEAD / parent. Order-balanced result = geomean of the
   two trial ratios. Raw JSON: `/tmp/groovy-12284-perf2/`.
   
   ---
   
   ## 4. Results
   
   ### 4.1 End-to-end indy (ops/ms)
   
   | Benchmark | T1 parent | T1 HEAD | T1 | T2 parent | T2 HEAD | T2 | Geomean |
   |---|---:|---:|---:|---:|---:|---:|---:|
   | `dynamic_arity1` | 659 888 ± 26 847 | 1 063 775 ± 65 319 | 1.612× | 658 
284 ± 25 333 | 964 529 ± 88 298 | 1.465× | **1.54×** |
   | `dynamic_arity2` | 496 882 ± 18 108 | 959 441 ± 37 131 | 1.931× | 489 092 
± 27 441 | 871 731 ± 140 016 | 1.782× | **1.86×** |
   | `dynamic_arity3` | 405 117 ± 10 264 | 759 771 ± 81 255 | 1.875× | 402 888 
± 25 325 | 796 490 ± 31 832 | 1.977× | **1.93×** |
   | `dynamic_arity4` | 130 981 ± 19 018 | 629 695 ± 22 114 | 4.808× | 137 525 
± 3 348 | 625 549 ± 18 672 | 4.549× | **4.68×** |
   | `dynamic_arity5` | 106 107 ± 3 115 | 114 814 ± 13 727 | 1.082× | 102 935 ± 
9 850 | 117 950 ± 7 992 | 1.146× | 1.11× |
   | `cs_arity2` | 1 570 710 ± 29 917 | 1 610 876 ± 47 158 | 1.026× | 1 578 283 
± 89 044 | 1 563 538 ± 70 906 | 0.991× | **1.01×** |
   | `cs_arity5` | 1 593 835 ± 73 043 | 1 538 583 ± 166 577 | 0.965× | 1 609 
468 ± 30 792 | 1 550 691 ± 63 009 | 0.963× | 0.96× |
   
   Treatment geomean arity 1–4: **2.25×**. Arity 1–3 only: **1.76×**.
   CS is clean on both trials (no dirty fork this run).
   
   ### 4.2 Allocation (`gc.alloc.rate.norm`)
   
   | Benchmark | Parent B/op | HEAD B/op |
   |---|---:|---:|
   | `cs_*` / `dynamic_arity1–3` | ≈ 0 | ≈ 0 |
   | `dynamic_arity4` | **32** | ≈ 0 |
   | `dynamic_arity5` | **40** | **40** |
   
   Same structural fingerprint as before the review follow-up.
   `Object[4]` = 32 B, `Object[5]` = 40 B (compressed oops). C2 still
   scalar-replaces `n ≤ 3`.
   
   ### 4.3 Isolated combinator (intra-HEAD)
   
   | Row | ops/ms | vs collector | vs chained `bindTo` |
   |---|---:|---:|---:|
   | `collector_arity1` | 173 850 ± 8 694 | — | — |
   | `specialised_arity1` (`insertArguments`) | 199 279 ± 5 964 | **1.15×** | — 
|
   | `collector_arity2` | 157 001 ± 6 634 | — | — |
   | `specialised_arity2` | 187 601 ± 4 442 | **1.19×** | — |
   | `collector_arity4` | 114 896 ± 2 409 | — | — |
   | `specialised_arity4` (`insertArguments`) | 147 758 ± 18 155 | **1.29×** | 
**0.99×** |
   | `specialised_bindTo_arity4` | 149 060 ± 23 010 | 1.30× | — |
   
   Jochen’s hypothesis that one `insertArguments` might beat four `bindTo`s
   does not show up as a throughput delta on this JDK (0.99×, CIs overlap).
   It is still the better code: one adapter, same JIT result, matches
   `IndyCompoundAssign`.
   
   ### 4.4 Calibration
   
   | Ruler | Parent | HEAD | Speedup (P/H) |
   |---|---:|---:|---:|
   | `cpuIntegerOps` | 413.2 ± 4.0 µs | 412.6 ± 12.0 µs | **1.00×** |
   | `memoryPointerChase` | 1557 ± 76 µs | 1534 ± 123 µs | **1.01×** |
   | `allocationChurn` | 91.3 ± 8.6 µs | 183 ± 470 µs | unusable (HEAD CI > 
mean) |
   
   Hardware did not drift. Ignore HEAD `allocationChurn` as before.
   
   ---
   
   ## 5. Conclusions
   
   The review follow-up does **not** give back the GROOVY-12284 win:
   
   - Linked monomorphic arity 1–3: **1.5–1.9×** (combinator / inlining).
   - Arity 4: **4.68×** and 32 B/op → 0.
   - Arity 5 and `@CompileStatic`: parity.
   - `insertArguments` vs chained `bindTo`: **0.99×** isolated; ship
     `insertArguments`.
   
   **Performance verification: PASS.**
   
   Keep `SameClassesGuardBench.dynamic_arity4` / `dynamic_arity5` under GC
   profiling in the indy regression rotation.
   
   ### Reproduction
   
   ```bash
   ./gradlew :test --tests 
org.codehaus.groovy.vmplugin.v8.IndySameClassesGuardTest
   ./gradlew :test
   ./gradlew :perf:jmh -PbenchInclude=SameClassesGuardBench 
-PjmhResultFormat=JSON
   ./gradlew :perf:jmh -PbenchInclude=SameClassesGuardBench -PjmhProfilers=gc 
-PjmhResultFormat=JSON
   ./gradlew :perf:jmh -PbenchInclude=SameClassesGuardMhBench 
-PjmhResultFormat=JSON
   ```
   
   Parent worktree: `/tmp/groovy-12284-parent` @ `e801df39`.
   Runner: `/tmp/groovy-12284-perf2/run.sh`.
   
   ---
   
   *Report generated from local JMH A/B measurements on 2026-08-22 after the PR 
#2822 review follow-up. Raw JSON: `/tmp/groovy-12284-perf2/`.*
   




> Specialize indy sameClasses guards for arity 1-4
> ------------------------------------------------
>
>                 Key: GROOVY-12284
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12284
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>
> h3. Problem
> When an invokedynamic site is linked with all arguments non-null and at least 
> one parameter type that is non-final (or a primitive wrapper — GROOVY-11782), 
> {{Selector}} installs a same-class guard:
> {code:java}
> SAME_CLASSES
>     .bindTo(expectedClasses)
>     .asCollector(Object[].class, n)
>     .asType(MethodType.methodType(boolean.class, pt));
> {code}
> {{asCollector}} of an {{Object}} array of length {{n}} allocates a fresh 
> array on *every later invocation* of that site; the array overload of 
> {{sameClasses}} then walks it.
> That is the hot path for ordinary dynamic Groovy calls of the shapes 
> {{recv.foo()}}, {{recv.foo(a)}}, {{recv.foo(a, b)}}, {{recv.foo(a, b, c)}} — 
> arity 1-4 (receiver plus 0-3 arguments). Dynamic indy sites almost always 
> have {{Object}} parameter types, so this guard is the common case, not a rare 
> fallback.
> (If any argument is {{null}} at link time, {{Selector}} already installs 
> per-slot {{SAME_CLASS}} / {{IS_NULL}} tests and does not use the collector.)
> The classic MOP already specializes this check: 
> {{MetaClassHelper.sameClasses}} has overloads for 0-4 arguments so the 
> call-site cache does not box arguments into an array. The indy guard did not.
> h3. Goal
> Keep the same guard semantics (return {{false}} if any argument is {{null}} 
> or has a different runtime class) without allocating an {{Object}} array on 
> the common 1-4 arity shapes.
> h3. Approach
> ||Arity (incl. receiver)||Guard||
> |0|constant {{true}}|
> |1|existing {{SAME_CLASS}}|
> |2|new {{SAME_CLASSES_2}}|
> |3|new {{SAME_CLASSES_3}}|
> |4|new {{SAME_CLASSES_4}}|
> |5 or more|existing {{SAME_CLASSES}} plus {{asCollector}} (unchanged)|
> Expected classes are bound with {{bindTo}}. One {{guardWithTest}} at the 
> site, via a single {{Selector.sameClassesGuard(args, pt)}} helper.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to