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

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

paulk-asert commented on PR #2823:
URL: https://github.com/apache/groovy/pull/2823#issuecomment-5383452701

   AI read:
   
   > **Verdict: technically sound, low risk, mergeable. Two things I'd want 
changed first (both small), and the PR description needs trimming before it 
becomes the commit/JIRA record.**
   > 
   > ## What it actually changes
   > 
   > Two independent optimisations plus a doc line:
   > 
   > 1. **Name-indexed extension-method cache.** `makeMethodsUnmodifiable` now 
wraps each per-receiver list in a private `MethodsByName` (an immutable 
`AbstractList` + `RandomAccess` carrying a `HashMap<String, 
List<MethodNode>>`). `findDGMMethodsForClassNode` replaces its linear 
`node.getName().equals(name)` scan with a hash lookup.
   > 2. **Lazy parameter erasure.** `chooseBestMethods` no longer 
unconditionally clones `Parameter[]`; the new `parametersForDistance` clones 
only on first generic/placeholder parameter and otherwise returns the original 
array.
   > 
   > ## Correctness — checked, not assumed
   > 
   > - **The name index is behaviour-identical.** The index key is 
`method.getName()`, the same predicate the old loop used, and the accumulator 
is still the same `TreeSet` with `DGM_METHOD_NODE_COMPARATOR`, so ordering and 
dedup are unchanged. The new test `'name index matches a linear scan of the 
cached list'` asserts exactly this equivalence.
   > - **The aliasing question resolves clean.** I traced every consumer of the 
array returned by `parametersForDistance` — 
`measureParametersAndArgumentsDistance` and its callees 
(`allParametersAndArgumentsMatch`, `firstParametersAndArgumentsMatch`, 
`lastArgMatchesVarg`, `excessArgumentsMatchesVargsParameter`, 
`getVarargsDistance`, `isVargs`). All are read-only; 
`firstParametersAndArgumentsMatch` copies into a fresh array rather than 
writing in place. So handing out `MethodNode`'s internal parameter array is 
safe as the code stands.
   > - **Immutability is preserved.** `AbstractList` throws 
`UnsupportedOperationException` for `add`/`set`/`remove`/`clear`/`sort` without 
overrides, so `MethodsByName` is as immutable as the 
`Collections.unmodifiableList` it replaces. The tests cover this.
   > - **Bonus: it fixes a real latent bug.** On master, 
`clearExtensionMethodCache(loader)` does 
`EXTENSION_METHOD_CACHE.cache.remove(loader)` 
(`StaticTypeCheckingSupport.java:279`) and never touches `preemptiveNamesCache` 
— despite the javadoc claiming the preempt set "shar[es] its loader lifecycle, 
so it never goes stale independently." That was untrue: the GROOVY-12115 
preempt-name set could survive a cache clear and go stale. The new 
`invalidate`/`invalidateAll` drop both, and there's a test for it. Worth 
calling out in the JIRA, because it's a correctness fix hiding inside a perf PR.
   > 
   > **CI is fully green** — JDK 17/21/25, Linux/macOS/Windows, all 
`additional` module shards, CodeQL, SonarCloud, and both JMH suites.
   > 
   > ## Two changes I'd ask for
   > 
   > **(a) The downcast is brittle.** In `AbstractExtensionMethodCache`:
   > 
   > ```java
   > return methods == null ? Collections.emptyList() : ((MethodsByName) 
methods).named(name);
   > ```
   > 
   > This assumes every map value came through the private 
`makeMethodsUnmodifiable`. True today. But `get(ClassLoader)` is **public and 
non-final** on a **public abstract** class, and `MacroMethodsCache` 
(groovy-macro) already subclasses it. Anyone overriding `get` — or any future 
alternate population path — turns this into a `ClassCastException` deep inside 
the type checker. Cheapest fix: make `get(ClassLoader)` `final`. Slightly 
better: hold `Map<String, MethodsByName>` internally and widen for the public 
view.
   > 
   > **(b) The lost `clone()` was an accidental safety barrier.** 
`MethodNode.getParameters()` returns the field, not a copy. It's safe now, but 
a future edit anywhere in the distance-measurement chain that writes 
`parameters[i] = ...` would silently corrupt the AST for the rest of the 
compile — no exception, wrong overload resolution. The javadoc mentions it; I'd 
want a blunter warning at the point where the original array is returned. The 
new tests assert array *identity* is preserved but not that elements are never 
mutated in place. Low probability, high blast radius.
   > 
   > ## Smaller notes (not blockers)
   > 
   > - **The macro cache pays for an index it never uses.** 
`MacroMethodsCache.getMethodMapper()` returns `m -> m.getName()`, so its cache 
keys are *method names*. `MethodsByName` then re-indexes each bucket by the 
same name — a degenerate one-entry `HashMap` plus a `singletonList` per key, 
for a `named()` lookup that groovy-macro never calls. Small in absolute terms, 
but pure waste; a `protected boolean indexByName()` hook or lazy index 
construction would avoid it.
   > - **Memory isn't quantified.** The PR quantifies everything else to the 
byte, but not the retained size of the new indexes. Per receiver key you now 
hold a `MethodNode[]` *plus* a `HashMap` *plus* a list per distinct name — 
across hundreds of keys, and multiplied per `ClassLoader` in app-server-style 
setups. Probably a few hundred KB and clearly worth it; I'd just want one 
sentence stating it.
   > - **The write-up overstates what the data shows.** By the PR's own 
numbers, `ChooseBestMethodBench` is *slower* in ns/op in 3 of 5 scenarios 
(926→972, 2303→2336, 1394→1466), with error bars far larger than the deltas — 
time is a wash there, and the defensible claim is the allocation reduction 
(which is exact and well argued). `StcCompilePerfBench` reports 416.65 ± 87.58 
vs 336.28 ± 56.97 — those intervals overlap substantially, so "1.24× speedup / 
−19.3%" is not established at the stated 99.9% confidence. The lookup 
microbenchmark speedups (3–10×) are large enough relative to their error to be 
real. I'd trim the claims to what the data supports, and strip the 
`file:///home/daniel/IdeaProjects/groovy/...` links before this lands in an ASF 
commit record.




> STC: index extension methods by name and skip cloning non-generic parameters
> ----------------------------------------------------------------------------
>
>                 Key: GROOVY-12285
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12285
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>
> The static type checker resolves DGM (Default Groovy Methods) and other 
> extension methods by walking the receiver hierarchy and collecting methods of 
> a given name. {{ExtensionMethodCache}} stores a flat list per receiver type, 
> so each named lookup scans every method on that type. Receivers such as 
> {{Object}} and {{Collection}} have hundreds of DGM methods, and that scan 
> sits on the compile hot path.
> {{chooseBestMethod}} erases generic parameter types before measuring 
> argument-parameter distance. It currently clones every candidate's parameter 
> array to do so, including methods that have no generic parameters.
> h3. Proposed change
> * When a class loader's extension methods are scanned, index each receiver 
> list by method name so a named lookup is a hash get rather than a linear scan.
> * Clone a candidate's parameter array only when at least one parameter is a 
> generics placeholder or otherwise uses generics.
> * Drop derived indexes together with the loader's method map so they cannot 
> go stale independently.
> {code:java}
> // today
> for (MethodNode node : fromDGM) {
>     if (node.getName().equals(name)) accumulator.add(node);
> }
> // proposed
> accumulator.addAll(EXTENSION_METHOD_CACHE.get(loader, className, name));
> {code}
> h3. Impact
> Compile-time only. Named lookup results and overload selection stay the same. 
> {{MethodNode}} parameter arrays are not mutated.



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

Reply via email to