[typemap] Reduce Java-to-managed proxy lookup allocations - #12619
Conversation
Add a direct first-result lookup over the cached proxy array and use it from the Java-to-managed singular type path. Keep the existing multi-result API for aliases and arrays, with focused allocation and alias coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cache the common single-proxy result directly, avoid iterator and eager collection allocations while resolving JNI names, and retain arrays only for aliases and misses. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs — 💡 suggestion: _jniProxyCache now stores object values and call sites assume the value is always… |
What changed in this PR
This PR optimizes the trimmable typemap Java→managed lookup path in Mono.Android by introducing a single-target lookup API and reshaping the JNI-name proxy cache to avoid allocations in the common “single proxy hit” case, while preserving alias ordering and missing-entry caching behavior.
Changes:
- Add
TrimmableTypeMap.TryGetTargetType()to return the first mapped target type without materializing all targets. - Change the JNI-name proxy cache to store either a single
JavaPeerProxyor aJavaPeerProxy[](aliases/misses), built via a newref-based proxy collector. - Extend tests to validate allocation-free repeated lookups and the new cache entry shapes (single vs alias vs miss).
| File | Description |
|---|---|
| tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs | Adds allocation and cache-shape tests for the new lookup/cache behavior. |
| tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapRuntimeCoverageTests.cs | Adds coverage asserting TryGetTargetType() returns the first alias-ordered target. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs | Switches the hot single-type lookup path to the new allocation-free TryGetTargetType(). |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs | Reworks JNI-name proxy caching to store a single proxy directly, adds TryGetTargetType(), and introduces JniProxyCacheBuilder. |
| src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs | Replaces iterator-based proxy enumeration with CollectProxyTypes(..., ref builder) to reduce allocations. |
| src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs | Updates the typemap interface to the new ref-builder collection model. |
| src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs | Adapts aggregate proxy collection to forward into universe collectors using the ref-builder. |
Resolve the single callable-wrapper proxy before creating the JNI type so native registration uses one shared path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pass resolved proxy instances to the cache builder so its Add operation is void while retaining one attribute materialization per proxy. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Expose a struct enumerable over cached proxy entries so multi-result JniTypeManager lookups no longer materialize a Type array. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Throw a clear InvalidOperationException when a fallback cache entry is not a JavaPeerProxy array, without adding work to the direct-proxy hot path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use an iterator over cached proxy entries instead of a custom struct enumerable while retaining allocation-free singular lookups. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
❌ Reject
Found 1 error: the native-registration path now registers only the first callable wrapper in an alias group, leaving callbacks from additional ACW proxies unregistered. The direct-proxy cache shape and allocation-focused singular lookup are otherwise well targeted, with useful focused coverage.
CI is still in progress; the completed CLA, Android Tools Tests (Mac), and Java.Interop Tests (Mac) checks are passing.
Generated by Android PR Reviewer for #12619 · gpt56 · 138.7 AIC · ⌖ 8.85 AIC · ⊞ 25.7K
Comment /review to run again
Register native methods for every callable-wrapper proxy in an alias cache entry while sharing one JNI type, with focused multi-wrapper coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
⚠️ Needs Changes (CI pending)
Findings: 0 errors, 0 warnings, 1 suggestion. The cache specialization preserves lookup ordering, missing-entry behavior, and all-proxy native registration; the focused tests cover the new cache shapes and allocation goal. One XML comment should be corrected to describe the single-surviving-alias shape accurately.
CI is still in progress: the completed checks are green, with the platform build jobs still running.
Generated by Android PR Reviewer for #12619 · gpt56 · 96.8 AIC · ⌖ 13.2 AIC · ⊞ 25.7K
Comment /review to run again
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Summary
JavaPeerProxy, using arrays only for aliases and missesrefbuilderList<JavaPeerProxy>allocation until a second proxy is foundPerformance
Measured on an arm64 Android emulator with .NET 11 and partial ReadyToRun:
The small cached-miss fallback cost is intentional: the first branch is optimized for the overwhelmingly common single-proxy hit.
Validation
Mono.Androidfornet11.0Mono.Android.NET-Testsapp