Skip to content

Use ulong.TryFormat for Content-Length values >= 1000 in Kestrel - #70136

Open
davidfowl wants to merge 4 commits into
mainfrom
perf/kestrel-writenumeric-tryformat
Open

davidfowl wants to merge 4 commits into
mainfrom
perf/kestrel-writenumeric-tryformat

Conversation

@davidfowl

@davidfowl davidfowl commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Use ulong.TryFormat for Content-Length values >= 1000 in Kestrel

Faster HTTP/1.1 Content-Length header writing for values >= 1000

Description

BufferExtensions.WriteNumeric (used by HttpResponseHeaders.CopyToFast to write Content-Length) handles values < 1000 inline, but anything larger, which covers most real response bodies, went through WriteNumericMultiWrite. That path reads a [ThreadStatic] scratch array, runs a div/mod-by-10 loop, and then copies the digits into the output.

This change formats directly into the output span with ulong.TryFormat (UTF-8, invariant). Only when the remaining span is too small does it fall back to formatting into the existing [ThreadStatic] scratch array (unchanged from main) and then calling BufferWriter.Write. That fallback lives in its own non-inlined method, and it keeps the existing behavior of filling the tail of the current span. The change adds no unsafe code, stackalloc, Unsafe/MemoryMarshal or custom vectorization; it uses only the BCL TryFormat API. BufferWriter.cs is untouched.

No public API changes (no PublicAPI.*.txt edits) and no behavior change: the output bytes are identical to main for every value.

I also added BufferWriterWriteNumericBenchmark (and a BufferWriterWriteAsciiBenchmark companion) to Kestrel's Microbenchmarks.

Benchmark

BufferWriterWriteNumericBenchmark.WriteNumeric (BDN InProcess, 2 vCPU Linux VM, 3 interleaved base/PR rounds, 1000 ops per invoke; re-measured after removing stackalloc):

Number main PR delta
5 1.10–1.12 ns 1.10–1.12 ns =
421 2.22–2.51 ns 2.21–2.25 ns = (noise)
4,096 9.61–9.65 ns 4.95–5.34 ns -47%
1,048,576 12.44–12.70 ns 6.91–7.16 ns -44%
123,456,789,012 17.16–17.25 ns 9.78–9.94 ns -43%

No allocations before or after.

Tests

  • Microsoft.AspNetCore.Server.Kestrel.Core.Tests: 9907 passed, 13 failed. All 13 failures are the AddressBinder/KestrelServerTests IPv6 tests, which fail the same way on main in this environment. The existing PipelineExtensionTests (WritesNumericToAscii, WritesNumericToAsciiFastPath, WritesNumericAcrossSpanBoundaries) pass. After the stackalloc removal, I re-ran PipelineExtensionTests|HttpResponseHeadersTests|ChunkWriterTests: 200 passed.

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

BufferExtensions.WriteNumeric inlines values < 1000, but larger values
(most real Content-Length values) went through a [ThreadStatic] scratch
array, a div/mod-by-10 loop and a span copy. Format directly into the
output span with ulong.TryFormat (UTF-8) instead, and only fall back to a
stackalloc'd temporary buffer when the value must be split across spans.

Adds BufferWriterWriteNumericBenchmark / BufferWriterWriteAsciiBenchmark.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 21:23
@github-actions github-actions Bot added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Oct 30, 2026

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.

🔵 Needs a closer look

The required frozen review bundle was not supplied, preventing validation of revision provenance, routed guidance completeness, and existing-feedback deduplication.

No open issues 🎉

What changed in this PR

This PR optimizes Kestrel’s HTTP/1.1 Content-Length formatting for values ≥ 1000 while retaining the existing inline path for smaller values.

Changes:

  • Replaces thread-local scratch storage and manual digit conversion with direct UTF-8 ulong.TryFormat, using a stack-allocated fallback across span boundaries.
  • Adds scoped span parameters so BufferWriter can accept stack-allocated input.
  • Adds numeric and ASCII-writing microbenchmarks.
File Description
src/​Shared/​ServerInfrastructure/​BufferWriter.cs Marks span inputs as scoped to support stack-allocated buffers.
src/​Shared/​ServerInfrastructure/​BufferExtensions.cs Uses direct numeric formatting with a separate cross-span fallback.
src/​Servers/​Kestrel/​perf/​Microbenchmarks/​BufferWriterExtensionsBenchmark.cs Adds benchmarks for numeric and ASCII writes.

Review effort: Balanced

Copy link
Copy Markdown
Member Author

/azp run aspnetcore-ci

The only CI failure is Components.WebView.StaticWebAssets.WebViewBuildBehaviorTests.Publish_ProjectReferenceToWebViewWithJsModuleRcl_SucceedsWithSingleModulesManifest on Windows Server x64. That test isn't related to this change, which only touches Kestrel's shared buffer helpers, so I'm rerunning it as a likely flake.

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

Copy link
Copy Markdown
Member Author

/azp run

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

@azure-pipelines

Copy link
Copy Markdown
Command 'run

**🤖' is not supported by Azure Pipelines.



Supported commands

  • help:
    • Get descriptions, examples and documentation about supported commands
    • Example: help "command_name"
  • list:
    • List all pipelines for this repository using a comment.
    • Example: "list"
  • run:
    • Run all pipelines or specific pipelines for this repository using a comment. Use this command by itself to trigger all related pipelines, or specify specific pipelines to run.
    • Example: "run" or "run pipeline_name, pipeline_name, pipeline_name"
  • where:
    • Report back the Azure DevOps orgs that are related to this repository and org
    • Example: "where"

See additional documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copy link
Copy Markdown
Member Author

/azp run aspnetcore-components-e2e

The only failure is VirtualizationTest.VariableHeight_HtmlTable_DoesNotProduceNestedTrElements in the Mono client/WebAssembly leg. The same test passed in the CoreCLR and Mono server legs, and it also failed on unrelated PR #70108, so I'm rerunning it as a flake.

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

Copy link
Copy Markdown
Member Author

CI status: I pushed an empty commit to retrigger CI. On that run, the Windows Server x64 platform tests that failed last time (WebViewBuildBehaviorTests) now pass.

The only failure on this run is aspnetcore-components-e2e → Blazor E2E tests on Linux (Mono client and WebAssembly), where 4 of 3,803 tests failed. The same leg passed on the first run against identical code, since the retrigger commit is empty. It's unrelated to this change, which only touches Kestrel's shared BufferExtensions/BufferWriter numeric formatting, so it looks flaky. Could a maintainer rerun that leg? I can't trigger reruns.

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

… leg)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copy link
Copy Markdown
Member Author

Pushed an empty commit to retrigger CI. /azp run isn't accepted from this account. The previous run's only failure was the Blazor E2E Mono client/WebAssembly leg (VirtualizationTest.VariableHeight_HtmlTable_DoesNotProduceNestedTrElements). It passed on the CoreCLR and Mono server legs, also fails on unrelated PR #70108, and a sibling VirtualizationTest is already quarantined (#69093). This PR only changes Kestrel's numeric header formatting.

🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

…buffer

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen

PureWeen commented Nov 1, 2026

Copy link
Copy Markdown
Member

/review

This branch has not been deployed

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

Labels

area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants