Skip to content

[TrimmableTypeMap] Reduce trimmable typemap data initialization startup cost #11501

@simonrozsival

Description

@simonrozsival

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.initialize is roughly 2/3 of the observed JNIEnvInit.Initialize budget

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 median
  • typemap.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.initialize during JNIEnvInit.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.Initialize spent in typemap.data.initialize, or document why the current cost is expected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssues that need to be assigned.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions