[cDAC] Cache resolved read-only metadata address per module in EcmaMetadata_1#128774
Open
max-charlamb wants to merge 1 commit into
Open
[cDAC] Cache resolved read-only metadata address per module in EcmaMetadata_1#128774max-charlamb wants to merge 1 commit into
max-charlamb wants to merge 1 commit into
Conversation
GetReadOnlyMetadataAddress walked the PE headers on every call and asked PEReader to also PrefetchMetadata, which slurped the entire metadata blob into memory and then discarded it (we only ever read PEHeaders.MetadataStartOffset/MetadataSize from the PEReader before disposing it). On dumps where the metadata pages are not all captured, the prefetch threw VirtualReadException before the method could cache its result, so every subsequent call repeated the full PE walk and threw again. The exceptions were swallowed by callers' fallbacks (e.g. SOSDacImpl.GetMethodTableName -> DacStreams.StringFromEEAddress), so functionality was unaffected but performance suffered badly. Drop PrefetchMetadata (we never use the prefetched bytes; callers read the metadata span themselves via target.ReadBuffer) and add a small per-handle cache that Flush() clears alongside _metadata. Also promote ModuleHandle to a record struct so it implements IEquatable<ModuleHandle> with a custom GetHashCode. The previous plain-struct version forced Dictionary<ModuleHandle, ...> lookups through ValueType.Equals/GetHashCode (reflection-based), which is roughly 6x slower than the compiler-generated equality. Measured on SOS.WebApp3.Heap.dmp via a local benchmark harness (GetMethodTableData + GetMethodTableName + GetMethodTableFieldData + GetObjectData over the full GC heap, 3 iterations): warm-iter reads: 99,198 -> 19,518 (-80%) warm-iter time: 0.19 s -> 0.14 s (-29% to -42%) total reads: 385,928 -> 150,351 (-61%) failed reads: 1,992 (unchanged) name ok/err: 686/0 (unchanged) Contributes to dotnet#128717. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
Contributor
There was a problem hiding this comment.
Pull request overview
Caches the resolved read-only metadata TargetSpan per ModuleHandle in EcmaMetadata_1 and drops the unused PrefetchMetadata flag from PEReader, eliminating repeated PE walks and per-call eager metadata reads that were throwing VirtualReadException on dumps with partial metadata pages. ModuleHandle is promoted to a record struct to give dictionary lookups compiler-generated value equality instead of reflection-based ValueType.Equals.
Changes:
- Add
_readOnlyMetadataAddresscache inEcmaMetadata_1, cleared inFlush()alongside_metadata. - Stop requesting
PEStreamOptions.PrefetchMetadatasince callers read metadata bytes themselves viatarget.ReadBuffer. - Convert
ModuleHandlefrom a hand-written readonly struct to areadonly record structfor fast value equality/hashing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs |
Adds per-module cache for the read-only metadata address and removes PrefetchMetadata. |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs |
Converts ModuleHandle to a readonly record struct for compiler-generated equality. |
steveisok
approved these changes
May 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes part of #128717.
GetReadOnlyMetadataAddresswalked the PE headers on every call and askedPEReaderto alsoPrefetchMetadata, which eagerly read the entire metadata blob into memory and then discarded it (we only ever readPEHeaders.MetadataStartOffset/MetadataSizefrom thePEReaderbefore disposing it).On dumps where the metadata pages are not all captured, the prefetch threw
VirtualReadExceptionbefore the method could cache its result, so every subsequent call repeated the full PE walk and threw again. The exceptions were swallowed by callers' fallbacks (e.g.SOSDacImpl.GetMethodTableName->DacStreams.StringFromEEAddress), so functionality was unaffected but performance suffered badly.This PR:
PrefetchMetadata(we never use the prefetched bytes; callers read the metadata span themselves viatarget.ReadBuffer).Flush()clears alongside_metadata.ModuleHandleto arecord structso dictionary lookups use compiler-generatedIEquatable<T>/GetHashCodeinstead of reflection-basedValueType.Equals(~6x faster in a microbenchmark).Measurements
Measured on
SOS.WebApp3.Heap.dmpvia a local benchmark harness (GetMethodTableData+GetMethodTableName+GetMethodTableFieldData+GetObjectDataover the full GC heap, 3 iterations):