Skip to content

Reuse typical instantiation DispatchMap for generic instantiations#130530

Open
davidwrighton wants to merge 2 commits into
dotnet:mainfrom
davidwrighton:dispatchmap-reuse-for-generic-instantiations
Open

Reuse typical instantiation DispatchMap for generic instantiations#130530
davidwrighton wants to merge 2 commits into
dotnet:mainfrom
davidwrighton:dispatchmap-reuse-for-generic-instantiations

Conversation

@davidwrighton

Copy link
Copy Markdown
Member

This PR contains two related optimizations to generic type loading / interface slot handling in the CoreCLR type loader.

1. Reuse typical instantiation DispatchMap for generic instantiations

When loading a non-typical instantiation of a generic type that undergoes a full MethodTableBuilder run (the __Canon canonical form and value-type instantiations such as List<int>), the interface DispatchMap was rebuilt from scratch via PlaceInterfaceMethods for every instantiation. The encoded DispatchMap is instantiation-independent (it stores type IDs and slot numbers), so it can instead be built once while constructing the type's typical instantiation and reused for all of its non-typical instantiations.

  • Release: PlaceInterfaceMethods is skipped for a non-typical instantiation when the typical instantiation's DispatchMap can be reused, and the typical instantiation's encoded map bytes are copied into the new MethodTable's inline DispatchMap.
  • Debug/Checked: the specific instantiation's DispatchMap is still built and asserted to be byte-for-byte identical to the typical instantiation's map, guarding the instantiation-independence invariant.

This is safe because PlaceInterfaceMethods only produces DispatchMap interface entries (it does not mutate the vtable; PlaceMethodImpls still always runs), and the two consumers that read the half-built dispatch map after PlaceInterfaceMethods (ValidateInterfaceMethodConstraints and VerifyVirtualMethodsImplemented) are already skipped for non-typical instantiations because fNoSanityChecks is TRUE for them.

2. Avoid iterating interface methods to size virtual-static slot table

bmtInterfaceEntry::CreateSlotTable walked every method of an interface that has virtual static methods, counting the static+virtual ones solely to size the bmtInterfaceSlotImpl array. The subsequent loop already recomputes the exact per-method placement, so the array can simply be over-allocated to the interface's method count, eliminating the extra MethodIterator walk.

Validation

Performance

Measured on an Rx cold-start micro-benchmark that isolates the System.Reactive construct+initialize type-loading path (interleaved A/B, 60 iterations each, release coreclr.dll swapped, baseline = before both changes):

Variant Median workload vs baseline
Baseline (neither change) 252.15 ms
DispatchMap reuse only 248.61 ms −3.07 ms (−1.2%)
Both changes 248.62 ms −3.53 ms (−1.4%)

Standard deviation was ~1.8 ms, so the ~1.4% improvement is a clear signal on type-loading-bound workloads. On WPF R2R startup (where type loading is a much smaller fraction of total startup) the effect is smaller and within run-to-run noise.

davidwrighton and others added 2 commits July 10, 2026 15:02
When loading a non-typical instantiation of a generic type that undergoes a
full MethodTableBuilder run (the __Canon canonical form and value-type
instantiations such as List<int>), the interface DispatchMap was rebuilt from
scratch via PlaceInterfaceMethods for every instantiation. The encoded
DispatchMap is instantiation-independent (it stores type IDs and slot numbers),
so it can instead be built once while constructing the type's typical
instantiation and reused for all of its non-typical instantiations.

In release builds, PlaceInterfaceMethods is skipped for a non-typical
instantiation when the typical instantiation's DispatchMap can be reused, and
the typical instantiation's encoded map bytes are copied into the new
MethodTable's inline DispatchMap.

In debug/checked builds the specific instantiation's DispatchMap is still built
and asserted to be byte-for-byte identical to the typical instantiation's map,
guarding the instantiation-independence invariant.

This is safe because PlaceInterfaceMethods only produces DispatchMap interface
entries (it does not mutate the vtable; PlaceMethodImpls still always runs), and
the two consumers that read the half-built dispatch map after
PlaceInterfaceMethods (ValidateInterfaceMethodConstraints and
VerifyVirtualMethodsImplemented) are already skipped for non-typical
instantiations because fNoSanityChecks is TRUE for them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3bea685d-50eb-4245-b1ea-4310df00c7d3
bmtInterfaceEntry::CreateSlotTable walked every method of an interface that has
virtual static methods, counting the static+virtual ones solely to size the
bmtInterfaceSlotImpl array. The subsequent loop already recomputes the exact
per-method placement, so the array can simply be over-allocated to the
interface's method count, eliminating the extra MethodIterator walk. The reused
pDeclMD from the placement loop replaces the redundant it.GetDeclMethodDesc()
call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3bea685d-50eb-4245-b1ea-4310df00c7d3
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes CoreCLR generic type loading by avoiding redundant work when building interface dispatch maps for non-typical generic instantiations, and by reducing per-interface iteration overhead when sizing virtual-static slot tables.

Changes:

  • Reuse the typical instantiation’s encoded DispatchMap for eligible non-typical generic instantiations (release), with byte-for-byte validation against a rebuilt map in _DEBUG/Checked builds.
  • Reduce work in bmtInterfaceEntry::CreateSlotTable by avoiding an extra MethodIterator pass when sizing storage for interfaces with virtual static methods.
  • Add a DispatchMap helper to expose the encoded map bytes for reuse/copying.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/coreclr/vm/methodtablebuilder.h Tracks typical instantiation MethodTable on all builds and adds helper declaration for DispatchMap reuse.
src/coreclr/vm/methodtablebuilder.cpp Implements DispatchMap reuse (skip PlaceInterfaceMethods in release when safe) and adjusts interface slot table sizing for virtual static methods.
src/coreclr/vm/contractimpl.h Adds DispatchMap::GetEncodedMapData() to access encoded map bytes for reuse/copying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants