Skip to content

[SRM] Optimize MetadataBuilder.GetOrAddConstantBlob.#121223

Open
teo-tsirpanis wants to merge 6 commits intodotnet:mainfrom
teo-tsirpanis:srm-write-constant
Open

[SRM] Optimize MetadataBuilder.GetOrAddConstantBlob.#121223
teo-tsirpanis wants to merge 6 commits intodotnet:mainfrom
teo-tsirpanis:srm-write-constant

Conversation

@teo-tsirpanis
Copy link
Contributor

@teo-tsirpanis teo-tsirpanis commented Oct 31, 2025

In MetadataBuilder.GetOrAddConstantBlob, the pooled blob builder was replaced by writing to a stack-allocated span, resulting in a 2x performance improvement when repeatedly writing the same scalar value (see comments in the bottom for benchmark results).

The logic of writing scalar constants was extracted to its own method that writes to a span.
This method also got used by `MetadataBuilder.GetOrAddConstantBlob`, avoiding the use of pooled blob builders when writing scalar constants.
Copilot AI review requested due to automatic review settings October 31, 2025 00:03
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 31, 2025
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection-metadata
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a comment

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 constant blob encoding by eliminating unnecessary allocations. It refactors the GetOrAddConstantBlob method and the WriteConstant methods to use stack-allocated buffers instead of pooled BlobBuilder instances for scalar (non-string) constants.

Key Changes:

  • Introduced a new WriteScalarConstant method that writes scalar constants to a Span<byte> and returns the number of bytes written
  • Refactored GetOrAddConstantBlob to use stack allocation for scalar values
  • Updated both WriteConstant overloads to call WriteScalarConstant for non-string values

Reviewed Changes

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

File Description
MetadataBuilder.Heaps.cs Refactored GetOrAddConstantBlob to use stack-allocated buffer and new WriteScalarConstant method
BlobWriterImpl.cs Added new WriteScalarConstant method and refactored existing WriteConstant overloads to use it

Comment on lines +151 to +152
/// <param name="bytes">The span where the content will be encoded.</param>
/// <param name="value">The constant value.</param>
Copy link

Copilot AI Oct 31, 2025

Choose a reason for hiding this comment

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

The documentation should clarify that bytes must be at least sizeof(ulong) bytes in length to accommodate the largest scalar type. Additionally, it should document the expected behavior when value is null (returns 4 bytes for a zero uint).

Suggested change
/// <param name="bytes">The span where the content will be encoded.</param>
/// <param name="value">The constant value.</param>
/// <param name="bytes">
/// The span where the content will be encoded. Must be at least <c>sizeof(ulong)</c> bytes in length to accommodate the largest scalar type.
/// </param>
/// <param name="value">
/// The constant value. If <paramref name="value"/> is <c>null</c>, writes 4 bytes representing a zero <c>uint</c>.
/// </param>

Copilot uses AI. Check for mistakes.
Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

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

This looks good to me. Nothing obvious jumps out and I assume we have good test coverage for this area.

@jkotas
Copy link
Member

jkotas commented Mar 17, 2026

optimizes

Do you have any perf numbers that show the benefit of this optimization?

@teo-tsirpanis

This comment was marked as off-topic.

@teo-tsirpanis

This comment was marked as off-topic.

@jkotas
Copy link
Member

jkotas commented Mar 18, 2026

MetadataBuilder.GetOrAddConstantBlob will be faster because of the use of stack-allocated memory instead of pooled blob builders, but it's a little hard to benchmark this method in isolation because there's no MetadataBuilder.Clear method yet.

GetOrAddConstantBlob is returning interned blob, so I would expect that calling it with the same value multiple times is going to be significantly faster. Am I missing something?

@jkotas
Copy link
Member

jkotas commented Mar 18, 2026

I do not think the benchmark you have shared above is exercising the code changed in this PR.

@teo-tsirpanis
Copy link
Contributor Author

I do not think the benchmark you have shared above is exercising the code changed in this PR.

Oops, sorry; I should stop working on multiple PRs at the same time.

GetOrAddConstantBlob is returning interned blob, so I would expect that calling it with the same value multiple times is going to be significantly faster. Am I missing something?

Yes; the idea is that serializing the blob to check if it exists would be faster, as it would use stack-allocated memory instead of a pooled BlobBuilder.

@teo-tsirpanis
Copy link
Contributor Author

Latest EgorBot results show a 2x slowdown for Blob(Builder|Writer).WriteConstant. There could be an argument made that the code is now deduplicated, and I still believe that MetadataBuilder.GetOrAddConstant will be a bit faster (can't think of any way to measure).

Alternatively, I'd be fine with optimizing just GetOrAddConstant by duplicating BlobWriterImpl.WriteConstant once more, or with closing this.

@teo-tsirpanis
Copy link
Contributor Author

I had an idea on how to benchmark MetadataBuilder.GetOrAddConstantBlob, and will try it later today.

Copilot AI review requested due to automatic review settings March 18, 2026 23:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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


You can also share your feedback on Copilot code review. Take the survey.

private void ClearBlobs()
{
_blobs.Clear();
_blobs.GetOrAdd([], [], default, out _);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Outdated.

@teo-tsirpanis
Copy link
Contributor Author

teo-tsirpanis commented Mar 19, 2026

I copied the minimum necessary subset of SRM's sources into a separate benchmark project, and compared the old and new implementations of MetadataBuilder.GetOrAddConstantBlob. The new implementation was found to be twice as fast:


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8037/25H2/2025Update/HudsonValley2)
AMD Ryzen 9 9950X 4.30GHz, 1 CPU, 32 logical and 16 physical cores
.NET SDK 11.0.100-preview.1.26104.118
  [Host]     : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v4
  DefaultJob : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v4


Method Mean Error StdDev Ratio Rank Gen0 Allocated Alloc Ratio
Old 489.8 ns 5.24 ns 4.91 ns 1.00 2 0.0019 32 B 1.00
New 239.3 ns 2.58 ns 2.41 ns 0.49 1 0.0019 32 B 1.00

Benchmark sources

I reverted all other parts of the PR and scoped it down to optimizing just MetadataBuilder.GetOrAddConstantBlob. I might take another try at BlobBuilder.WriteConstant in the future.

@teo-tsirpanis teo-tsirpanis changed the title [SRM] Deduplicate and optimize BlobWriterImpl.WriteConstant. [SRM] Optimize MetadataBuilder.GetOrAddConstantBlob. Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Reflection.Metadata community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants