Repository navigation
[TrimmableTypeMap] Reduce trimmable typemap data initialization startup cost #11501
Description
Summary
Trimmable typemap startup currently spends most of the managed JNIEnvInit.Initialize budget loading and initializing generated managed typemap data.
Using the OTEL instrumentation from draft PR #11502 and viewing the data through Aspire, a Release/CoreCLR/trimmable HelloWorld run shows:
- median
jnienv.initialize: 99,591 us - median
typemap.data.initialize: 62,103 us typemap.data.initializeis roughly 2/3 of the observedJNIEnvInit.Initializebudget
This should be tracked separately from the broader trimmable typemap runtime performance work because it points at the generated managed typemap initialization path itself, not just app activation or steady-state lookup cost.
Subtask of #10788.
10x trimmable data
From 10 Release/CoreCLR/trimmable launches of samples/HelloWorld:
| Metric | Count | Min | Median | Max | Mean |
|---|---|---|---|---|---|
TotalTime ms |
10 | 528.0 | 544.5 | 557.0 | 543.5 |
WaitTime ms |
10 | 531.0 | 549.5 | 564.0 | 548.8 |
| Host elapsed us | 10 | 624504.0 | 661617.0 | 746665.0 | 667727.7 |
jnienv.initialize us |
10 | 96694.2 | 99591.25 | 122651.1 | 101759.32 |
typemap.data.initialize us |
10 | 60933.0 | 62103.2 | 82455.6 | 64033.28 |
10x LLVM-IR comparison data
After extending the same OTEL instrumentation to the existing LLVM-IR/CoreCLR typemap path, 10 Release/CoreCLR/LLVM-IR launches show:
| Metric | Count | Min | Median | Max | Mean |
|---|---|---|---|---|---|
TotalTime ms |
10 | 470.0 | 480.5 | 492.0 | 480.2 |
WaitTime ms |
10 | 473.0 | 485.0 | 494.0 | 484.4 |
| Host elapsed us | 10 | 577463.0 | 605775.0 | 757226.0 | 635601.4 |
| LLVM span count | 10 | 36.0 | 103.5 | 141.0 | 96.0 |
jnienv.initialize us |
10 | 37423.2 | 38970.45 | 40533.8 | 39009.79 |
typemap.llvm.activation us |
10 | 900.6 | 986.5 | 1096.3 | 994.73 |
typemap.llvm.lookup_jni_name.uncached us |
10 | 78.7 | 93.3 | 127.9 | 99.24 |
Paired comparison
The directly paired startup boundary is jnienv.initialize. Trimmable is slower by about 60.6 ms median, and typemap.data.initialize accounts for about 62.1 ms median. That means the measured trimmable startup delta is almost entirely explained by generated typemap data initialization.
| Metric | Trimmable median | LLVM-IR median | Delta | Ratio |
|---|---|---|---|---|
TotalTime |
544.5 ms | 480.5 ms | +64.0 ms | 1.13x |
WaitTime |
549.5 ms | 485.0 ms | +64.5 ms | 1.13x |
| Host elapsed | 661,617 us | 605,775 us | +55,842 us | 1.09x |
jnienv.initialize |
99,591.25 us | 38,970.45 us | +60,620.8 us | 2.56x |
Paired / analogous spans
| Concept | Trimmable span | Trimmable median | LLVM-IR span | LLVM-IR median | Notes |
|---|---|---|---|---|---|
| JNI runtime init boundary | jnienv.initialize |
99,591.25 us | jnienv.initialize |
38,970.45 us | Directly comparable boundary. |
| Typemap data setup | typemap.data.initialize |
62,103.2 us | none | - | LLVM-IR has no managed generated typemap assembly load equivalent. This is the main trimmable-only cost. |
| Runtime typemap object setup | typemap.initialize |
4,249.3 us | none | - | Trimmable wraps generated dictionaries in runtime lookup structures. |
| Native registration setup | typemap.register_native_methods |
169.9 us | built-in/native typemap path | - | Not currently separated in LLVM-IR; native path is implicit. |
| Java object -> managed peer creation | typemap.peer.create |
1,005.4 us | typemap.llvm.activation |
986.5 us | Similar order of magnitude for first activity-related activation. |
| Java object lookup | typemap.lookup.java_object |
583.95 us | typemap.llvm.activation.peek_object + activation subspans |
8.3 us for peek_object; 986.5 us full activation |
Trimmable lookup includes more proxy resolution work; LLVM activation breakdown is more granular. |
| JNI-name lookup | typemap.lookup.jni_name |
74.25 us | typemap.llvm.lookup.jni_name |
201.1 us | Direct lookup concept, but implementation differs. LLVM-IR calls into native typemap metadata; trimmable uses managed dictionaries/proxy attributes. |
| Uncached JNI-name lookup | typemap.lookup.jni_name.uncached |
64.95 us | typemap.llvm.lookup.jni_name.uncached |
93.3 us | Directly comparable cache-miss path; both are sub-millisecond. |
| Managed-type lookup | typemap.lookup.managed_type |
63.2 us | no direct measured equivalent yet | - | LLVM-IR managed-to-Java lookup exists elsewhere (clr_typemap_managed_to_java) and should be instrumented separately if needed. |
| Uncached managed-type lookup | typemap.lookup.managed_type.uncached |
57.55 us | no direct measured equivalent yet | - | Same as above. |
| Register natives for generated proxy | typemap.on_register_natives |
844.55 us | no direct measured equivalent | - | Trimmable-specific native registration callback. |
Interpretation
The lookup and activation paths are not where the big delta is:
- Trimmable uncached JNI-name lookup median: 64.95 us
- LLVM-IR uncached JNI-name lookup median: 93.3 us
- Trimmable peer creation median: 1,005.4 us
- LLVM-IR activation median: 986.5 us
The large difference is trimmable-only startup work:
typemap.data.initialize: 62.1 ms mediantypemap.initialize: 4.25 ms median
Those two values line up with the observed JNIEnvInit.Initialize delta vs LLVM-IR.
Raw data
The raw local capture is in trimmable-typemap-otel-data.md on draft PR #11502.
Local artifacts used while preparing the issue:
artifacts/trimmable-typemap-otel-runs/20260526-115846
artifacts/llvm-typemap-otel-runs/20260526-130206
Each run directory contains am-start.txt, host timing, logcat, and Aspire trace/span JSON snapshots.
Related work that may change the results
- [TrimmableTypeMap] Package ReadyToRun trimmable typemap assemblies #11473 packages ReadyToRun trimmable typemap assemblies. Once that lands, rerun the same measurements because missing R2R packaging may inflate typemap initialization cost.
- Framework ACW trimming and ProGuard generator improvements may reduce payload size and generated wrapper pressure, but this issue is specifically about the cost of
typemap.data.initializeduringJNIEnvInit.Initialize.
Acceptance criteria
- Re-run startup measurements after [TrimmableTypeMap] Package ReadyToRun trimmable typemap assemblies #11473 lands.
- Identify the dominant cost inside
typemap.data.initialize. - Decide whether generated typemap data shape, loading strategy, R2R coverage, or lazy initialization should change.
- Reduce the share of
JNIEnvInit.Initializespent intypemap.data.initialize, or document why the current cost is expected.
Activity