Skip to content

Conversation

MihaZupan
Copy link
Member

When adding/reading headers where we don't have a special parser, "parsing" only validates that there are no new lines in the value. This change special-cases this (common) case and avoids allocating the HeaderStoreItemInfo.

Existing code paths where only non-validating APIs are used stay the same.
Overhead for cases of reading headers with validation, where they were added without validation, and we do have a known parser (still common) is minimal and an acceptable tradeoff IMO (extra branch).

Method Toolchain Mean Error Ratio Allocated Alloc Ratio
Add main 44.31 ns 0.386 ns 1.00 32 B 1.00
Add pr 21.35 ns 0.018 ns 0.48 - 0.00
AddEnumerable main 36.27 ns 0.592 ns 1.00 32 B 1.00
AddEnumerable pr 28.80 ns 0.265 ns 0.79 - 0.00
GetValues main 92.39 ns 0.302 ns 1.00 64 B 1.00
GetValues pr 40.35 ns 0.090 ns 0.44 32 B 0.50
AddAndGetValues main 94.01 ns 0.192 ns 1.00 64 B 1.00
AddAndGetValues pr 42.92 ns 0.229 ns 0.46 32 B 0.50
CloneHeaders main 905.95 ns 1.891 ns 1.00 1112 B 1.00
CloneHeaders pr 490.74 ns 1.064 ns 0.54 600 B 0.54
Benchmark code
BenchmarkRunner.Run<HeadersBench>(args: args);

[MemoryDiagnoser(false)]
public class HeadersBench
{
    private readonly HttpResponseHeaders _headers = new HttpResponseMessage().Headers;
    private readonly HttpRequestHeaders _headersToClone = new HttpRequestMessage().Headers;
    private readonly string[] _fooAsArray = ["Foo"];

    public HeadersBench()
    {
        _headersToClone.TryAddWithoutValidation("priority", "u=0, i");
        _headersToClone.TryAddWithoutValidation("sec-ch-ua-mobile", "?0");
        _headersToClone.TryAddWithoutValidation("sec-ch-ua-platform", "\"Windows\"");
        _headersToClone.TryAddWithoutValidation("sec-fetch-dest", "document");
        _headersToClone.TryAddWithoutValidation("sec-fetch-mode", "navigate");
        _headersToClone.TryAddWithoutValidation("sec-fetch-site", "none");
        _headersToClone.TryAddWithoutValidation("sec-fetch-user", "?1");
        _headersToClone.TryAddWithoutValidation("upgrade-insecure-requests", "1");
    }

    [Benchmark]
    public void Add()
    {
        _headers.Add("X-Custom", "Foo");
        _headers.Clear();
    }

    [Benchmark]
    public void AddEnumerable()
    {
        _headers.Add("X-Custom", _fooAsArray);
        _headers.Clear();
    }

    [Benchmark]
    public object GetValues()
    {
        _headers.TryAddWithoutValidation("X-Custom", "Foo");
        IEnumerable<string> values = _headers.GetValues("X-Custom");
        _headers.Clear();
        return values;
    }

    [Benchmark]
    public object AddAndGetValues()
    {
        _headers.Add("X-Custom", "Foo");
        IEnumerable<string> values = _headers.GetValues("X-Custom");
        _headers.Clear();
        return values;
    }

    [Benchmark]
    public HttpRequestHeaders CloneHeaders()
    {
        HttpRequestHeaders newHeaders = new HttpRequestMessage().Headers;

        foreach (KeyValuePair<string, IEnumerable<string>> header in _headersToClone)
        {
            newHeaders.Add(header.Key, header.Value);
        }

        return newHeaders;
    }
}

@MihaZupan MihaZupan added this to the 10.0.0 milestone Jun 26, 2025
@MihaZupan MihaZupan self-assigned this Jun 26, 2025
@Copilot Copilot AI review requested due to automatic review settings June 26, 2025 23:02
Copy link
Contributor

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

Copy link
Contributor

@Copilot 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 header handling by bypassing the full parser for headers without a known parser, special‐casing single‐value collections to avoid allocations, and updating both TryGetValues and the enumerator to reduce heap traffic.

  • Bypass parser and allocation when descriptor.Parser is null in both single- and multi-value Add overloads
  • Short-circuit single-element IEnumerable<string?> to reuse the single-value Add path
  • Optimize TryGetValues and GetEnumeratorCore to return raw string values without allocating HeaderStoreItemInfo

Copy link
Member

@CarnaViire CarnaViire left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

@MihaZupan MihaZupan merged commit 877b0cd into dotnet:main Jul 2, 2025
84 of 89 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Aug 2, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants