Repository navigation
[TrimmableTypeMap] Performance considerations #10795
Description
Activity
- addedArea: NativeAOTIssues that only occur when using NativeAOT.Issues that only occur when using NativeAOT.Area: CoreCLRIssues that only occur when using CoreCLR.Issues that only occur when using CoreCLR.
on Feb 10, 2026 - addedneeds-triageIssues that need to be assigned.Issues that need to be assigned.
on Feb 10, 2026 UTF-8 → span lookup: benchmark data
Investigated the cost of JNI type-name lookup with and without string allocation, using a managed simulation of the
NativeHashtable(keys stored as UTF-8 bytes, sameTypeHashingAlgorithms.ComputeNameHashCodehash, same ASCII fast-path key comparison asNativeReader.StringEquals).Two end-to-end paths benchmarked
Path A — current approach:
UTF-8 ROS<byte> → Encoding.UTF8.GetString() → string → TryGetValue(string)Path B — proposed approach:
UTF-8 ROS<byte> → Ascii.ToUtf16() into stackalloc char[] → TryGetValue(ReadOnlySpan<char>)Results (.NET 10, Apple M1 Max, Arm64 RyuJIT)
Method Input Mean Allocated LookupViaStringjava/lang/String(16B)31.8 ns 56 B LookupViaSpanjava/lang/String(16B)20.7 ns 0 B LookupViaStringcom/example/pkg/ParentClass$ChildClass(38B)67.6 ns 104 B LookupViaSpancom/example/pkg/ParentClass$ChildClass(38B)47.7 ns 0 B LookupViaStringcom/example/pkg/some/deep/nested/SomeFactory(44B)70.8 ns 112 B LookupViaSpancom/example/pkg/some/deep/nested/SomeFactory(44B)53.9 ns 0 B The span path is consistently ~30% faster with zero heap allocation across all input sizes.
The saving is larger than just the allocation cost (~4 ns) because the string path also pays GC write barrier costs as the returned
Typereference flows through the call.What would need to change in the runtime
The optimization requires adding a
TryGetValue(ReadOnlySpan<char>)overload to the NativeAOT type-map lookup path. Concretely:ExternalTypeMapDictionary(TypeMapLazyDictionary.NativeAot.cs:136) — add span-basedTryGetValueNativeParser.StringEquals(NativeFormatReader.String.cs:29) — addStringEquals(ReadOnlySpan<char>)overloadTypeHashingAlgorithms.ComputeNameHashCode(TypeHashingAlgorithms.cs:29) — addReadOnlySpan<char>overload (trivial; the algorithm already works character-by-character)- The public
IReadOnlyDictionary<string, Type>interface would need a companion API or the call site would need to downcast to the concrete type to access the span overload
Note: an even more aggressive optimization would be to add
TryGetValue(ReadOnlySpan<byte>)— since JNI names are ASCII, the UTF-8 bytes equal the char values, so the same hash and comparison work on raw bytes, skipping theAscii.ToUtf16step entirely. That would requireComputeNameHashCode(ReadOnlySpan<byte>)(already exists asComputeASCIINameHashCodein the tools, with a small bug on line 60) andStringEquals(ReadOnlySpan<byte>)onNativeParser.- added a commit that references this issue
on May 27, 2026
Part of #10788