Skip to content

[System.Text.Json] Fix ARM64 Utf8JsonReader regression - #132452

Merged
jozkee merged 2 commits into
dotnet:mainfrom
jozkee:fix-utf8jsonreader-arm64-pattern-regression
Aug 18, 2026
Merged

[System.Text.Json] Fix ARM64 Utf8JsonReader regression#132452
jozkee merged 2 commits into
dotnet:mainfrom
jozkee:fix-utf8jsonreader-arm64-pattern-regression

Conversation

@jozkee

@jozkeejozkee commented Aug 18, 2026

Copy link
Copy Markdown
Member

Fixes#131600.

Restore the explicit comparison form for the number terminator check in Utf8JsonReader.TryGetNumber. The pattern-matching form is semantically equivalent, but introduced a large Linux ARM64 regression in System.Text.Json.Tests.Perf_Get.GetUInt64.

Performance

EgorBot on an Azure Ampere ARM64 machine measured the regressed pattern form against the selectively restored reader code:

StateMeanRatio
Pattern form (43560bc7)900.8 ns1.00
Restored reader comparisons515.4 ns0.57

The cumulative bisection further narrowed the transition from 515.6 ns at fa936cfa to approximately 896 ns after this condition changed. The intervening commits only modify Debug.Assert expressions in Release builds.

Results:

Note

This pull request description was prepared with GitHub Copilot.

Restore the comparison form that produces efficient ARM64 code in TryGetNumber.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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 adjusts Utf8JsonReader’s numeric parsing logic by replacing a byte pattern-matching check with explicit comparisons in the TryGetNumber integer-digit path, aiming to address an ARM64 performance regression in number reading.

Changes:

  • Replaced is not ((byte)'.' or (byte)'E' or (byte)'e') with explicit != comparisons in the non-leading-zero integer parsing path.
  • Kept the surrounding control flow and exception behavior intact, focusing the change narrowly on the terminator check shape.
Suppressed comments (1)

src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs:1512

  • This change restores explicit comparisons in the non-leading-zero integer path, but the same “number terminator” check still uses the pattern-matching form in ConsumeZero (nextByte is not ((byte)'.' or (byte)'E' or (byte)'e')) which is also reached via TryGetNumber when the value starts with '0'. If the goal is to fully avoid the regressed code shape for number parsing, this should likely be updated as well to keep the parser consistent across both leading-zero and non-leading-zero paths.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}

@eiriktsarpalis

eiriktsarpalis commented Aug 18, 2026

Copy link
Copy Markdown
Member

Approving this to unblock the regression, but I think we might want to look closer at how pattern matching codegen is handled here cc @dotnet/roslyn

@jjonescz

Copy link
Copy Markdown
Member

Possibly related: dotnet/roslyn#80052

@jozkee

This comment was marked as outdated.

Apply the ARM64-friendly comparison form to leading-zero and multi-segment parsing paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 18, 2026 16:00
@jozkee

Copy link
Copy Markdown
MemberAuthor

I checked the IL for every pattern shape touched in this PR using .NET Lab. This is the Roslyn lowering tracked by dotnet/roslyn#80052.

In every shape, the is-pattern form lowers to strictly heavier IL than the equivalent &&/|| comparison chain.

Shapeis methodsize / locals / maxstack!=/== methodsize / locals / maxstack
A: not (. or E or e)Test132 B, 1 local, stack 2Test222 B, 0 locals, stack 8
B: not (E or e)Test325 B, 1 local, stack 2Test417 B, 0 locals, stack 8
C: + or -Test522 B, 1 local, stack 2Test614 B, 0 locals, stack 8
D: e or ETest722 B, 1 local, stack 2Test814 B, 0 locals, stack 8
E: not (Slash or Asterisk)Test925 B, 1 local, stack 2Test1017 B, 0 locals, stack 8

CopilotAI 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

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

@jozkee
jozkee enabled auto-merge (squash) August 18, 2026 17:02
@jozkee
jozkee merged commit 94157b0 into dotnet:mainAug 18, 2026
78 checks passed
@jozkee
jozkee deleted the fix-utf8jsonreader-arm64-pattern-regression branch August 18, 2026 19:35
@jozkee

Copy link
Copy Markdown
MemberAuthor

/backport to release/11.0-rc1

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0-rc1 (link to workflow run)

Sign up for freeto 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.

[Perf] Linux/arm64: 1 Regression on 7/22/2026 1:13:11 AM +00:00

4 participants

@jozkee@eiriktsarpalis@jjonescz