Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Test Utf8JsonReader code shape for ARM64 regression by jozkee · Pull Request #132430 · dotnet/runtime · GitHub
Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Test Utf8JsonReader code shape for ARM64 regression by jozkee · Pull Request #132430 · dotnet/runtime · GitHub
Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Test Utf8JsonReader code shape for ARM64 regression by jozkee · Pull Request #132430 · dotnet/runtime · GitHub
Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Test Utf8JsonReader code shape for ARM64 regression by jozkee · Pull Request #132430 · dotnet/runtime · GitHub
Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Test Utf8JsonReader code shape for ARM64 regression by jozkee · Pull Request #132430 · dotnet/runtime · GitHub
Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Test Utf8JsonReader code shape for ARM64 regression by jozkee · Pull Request #132430 · dotnet/runtime · GitHub
Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Test Utf8JsonReader code shape for ARM64 regression by jozkee · Pull Request #132430 · dotnet/runtime · GitHub
Skip to content

Test Utf8JsonReader code shape for ARM64 regression - #132430

Closed
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns
Closed

Test Utf8JsonReader code shape for ARM64 regression#132430
jozkee wants to merge 18 commits into
mainfrom
jozkee-perf-bisect-131600-reader-patterns

Conversation

@jozkee

Copy link
Copy Markdown
Member

Tests whether the compound pattern rewrites in the single-segment Utf8JsonReader affect the Linux ARM64 Perf_Get.GetUInt64 regression tracked by #131600.

This draft is stacked on #132399 only to retain the preserved #130976 source commits and the benchmark-image libunwind prerequisite. It does not modify #132399.

The benchmark should compare:

  • 66b30d95: last measured good source stage
  • 43560bc7: first measured bad source stage
  • e92960b3: Utf8JsonReader.cs compound patterns restored to their previous code shape

Validation:

  • dotnet build src/libraries/System.Text.Json/src/System.Text.Json.csproj
  • dotnet build /t:test src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
    • net11.0: 53,694 passed
    • net481: 53,382 passed

Note

This pull request was prepared with GitHub Copilot.

eiriktsarpalisand others added 8 commits July 20, 2026 16:24
Use C# 14 field-backed properties for private state used only by accessors, and expression-bodied members for single-expression methods.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Replace built-in null comparisons and stable compound comparisons with equivalent C# patterns. Retain reflection comparisons that bind user-defined equality operators.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
Use the C# 14 field keyword while preserving mutable-schema validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69ae6b80-f573-41bd-8cf9-e0f858510bbe
…dText.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the compound pattern rewrites in the single-segment reader to isolate the ARM64 GetUInt64 regression in #131600.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 17, 2026 21:06
@jozkee

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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 a few hot-path code shapes (notably in Utf8JsonReader) to help determine whether recent compound-pattern rewrites are implicated in the Linux/ARM64 System.Text.Json.Tests.Perf_Get.GetUInt64 regression tracked by #131600.

Changes:

  • Replaces several C# pattern-based comparisons in Utf8JsonReader with equivalent ==/!= and &&/|| forms to restore an earlier code shape.
  • Restores a pre-modernization ternary shape for JsonEncodedText.GetHashCode().
  • Updates NativeAOT’s libunwind symbol-privatization step to avoid invoking llvm-link as the relocatable linker by preferring ld.lld/ld when necessary.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.csRewrites several compound/pattern comparisons in number parsing and token classification to different boolean forms for code-shape testing.
src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.csChanges GetHashCode() to a ternary form to match a prior code shape.
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txtAdds linker selection logic for the libunwind privatization custom command to avoid using llvm-link.
Suppressed comments (4)

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

  • Use byte literals for these UTF-8 byte checks to avoid char-to-int promotions and to match the rest of the reader's byte-oriented parsing logic.
 if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedEndOfDigitNotFound, nextByte);
}
}
Debug.Assert(nextByte == '.' || nextByte == 'E' || nextByte == 'e');

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

  • These are byte values from the UTF-8 payload; comparing to char literals promotes to int. Prefer (byte)'E'/(byte)'e' here (and in the assert) to keep the parsing logic purely byte-based.
 if (nextByte != 'E' && nextByte != 'e')
{
_bytePositionInLine += i;
ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedNextDigitEValueNotFound, nextByte);
}
}
Debug.Assert(nextByte == 'E' || nextByte == 'e');

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

  • Use byte literals for these UTF-8 byte comparisons to avoid implicit promotions and keep parsing code consistent with other (byte)'0' usage in this file.
 nextByte = data[i];
if (nextByte != '.' && nextByte != 'E' && nextByte != 'e')
{

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

  • Since nextByte is a byte from the UTF-8 data, prefer comparing against byte literals to avoid implicit numeric promotion (and to keep the parsing logic byte-oriented).
 byte nextByte = data[i];
if (nextByte == '+' || nextByte == '-')
{

@jozkee

This comment was marked as outdated.

jozkeeand others added 10 commits August 17, 2026 16:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jozkee

Copy link
Copy Markdown
MemberAuthor

Comparing the pattern-rewrite commit with the current PR head, which selectively restores the Utf8JsonReader expressions. Both full SHAs were verified against PR metadata and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits 43560bc --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

CopilotAI review requested due to automatic review settings August 17, 2026 23:41
@jozkee

Copy link
Copy Markdown
MemberAuthor

First-pass cumulative bisection of the confirmed Utf8JsonReader code-shape regression. The stages split the ten one-line restores into candidate groups of 1, 4, 3, and 2 changes. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,c514b6e24c36c921ccaf7ea6c95f528c52694b63,9d37db381a99a26be0e757f6aa56f96b9adf429c --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

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 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt:44

  • ld.lld is advertised as the preferred linker when CMAKE_LINKER resolves to llvm-link, but the current find_program(... NO_DEFAULT_PATH) only searches the llvm-link directory. If ld.lld is available on PATH (or via CMake defaults) but not colocated with llvm-link, the build will silently fall back to ld (or even fail if ld isn’t present) despite ld.lld being available. Consider falling back to a default-path find_program for ld.lld before trying ld.
 find_program(NATIVEAOT_PRIVATE_LIBUNWIND_LD_LLD
NAMES "ld.lld${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_VERSION}${NATIVEAOT_PRIVATE_LIBUNWIND_LINKER_EXTENSION}" ld.lld
PATHS "${NATIVEAOT_PRIVATE_LIBUNWIND_TOOL_DIR}"
NO_DEFAULT_PATH)

@jozkee

Copy link
Copy Markdown
MemberAuthor

Final split for the recovery boundary from issue #509. fa936cfa restores IsTokenTypeString, which is used by ValueTextEquals; 69b2a7cb restores the Release-effective number-terminator condition in TryGetNumber. Every full SHA was verified against the current PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits d354506,fa936cfa0ec1e7d1f634fa8885fd3a5519b2f432,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

ARM64 optimized disassembly at the confirmed regression boundary. This limits BenchmarkDotNet to one warmup and one measured invocation while requesting disassembly for TryGetNumber and GetUInt64. Both full SHAs were verified against the published PR history and local Git.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --minIterationCount 1 --maxIterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Retrying the ARM64 disassembly run after issue #511 was rejected because MinIterationCount and MaxIterationCount were both 1. This keeps one fixed warmup and measurement iteration but leaves the valid min/max defaults intact. Both full SHAs were reverified.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "System.Text.Json.Tests.Perf_Get.GetUInt64" --warmupCount 1 --iterationCount 1 --invocationCount 1 --unrollFactor 1 --envvars "DOTNET_JitDisasm:TryGetNumber GetUInt64" DOTNET_JitDisasmDiffable:1 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0

Note

This benchmark request was prepared with GitHub Copilot.

@jozkeejozkee closed this Aug 18, 2026
@jozkee
jozkee deleted the jozkee-perf-bisect-131600-reader-patterns branch August 18, 2026 17:04
@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks added by dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 across the same ARM64 regression boundary used above.

@EgorBot -ubuntu24_azure_ampere -pr 132430 -commits fa936cf,69b2a7cbfc7dfc8031deacace4bd770255522706 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

Note

This benchmark request was prepared with GitHub Copilot.

@jozkee

Copy link
Copy Markdown
MemberAuthor

Running the three System.Text.Json benchmarks from dotnet/performance commit 41fcabd6f5dc929253086bd117c4adaf24c83ed9 against PR #132504 on the same ARM64 Ampere target used above.

@EgorBot -ubuntu24_azure_ampere -pr 132504 --filter "Perf_ElementParseValue" "Perf_CommentLineSeparators" "Perf_ValueTextEquals"

usingSystem;usingSystem.Buffers;usingSystem.Text;usingSystem.Text.Json;usingBenchmarkDotNet.Attributes;publicclassPerf_ElementParseValue{privatebyte[]_string=null!;privatebyte[]_number=null!;privatebyte[]_object=null!;[GlobalSetup]publicvoidSetup(){_string=Encoding.UTF8.GetBytes("\"a short json string value\"");_number=Encoding.UTF8.GetBytes("123456789");_object=Encoding.UTF8.GetBytes("{\"value\":123456789}");}[Benchmark]publicJsonValueKindParseString()=>Parse(_string);[Benchmark]publicJsonValueKindParseNumber()=>Parse(_number);[Benchmark]publicJsonValueKindParseObject()=>Parse(_object);privatestaticJsonValueKindParse(byte[]utf8Json){varreader=newUtf8JsonReader(utf8Json);returnJsonElement.ParseValue(refreader).ValueKind;}}publicclassPerf_CommentLineSeparators{privateconstintSegmentSize=100;[Params(JsonCommentHandling.Skip,JsonCommentHandling.Allow)]publicJsonCommentHandlingCommentHandling;[Params(false,true)]publicboolMultiSegment;privatebyte[]_jsonPayload=null!;privateReadOnlySequence<byte>_jsonPayloadSequence;[GlobalSetup]publicvoidSetup(){_jsonPayload=Encoding.UTF8.GetBytes("{}//"+newstring('\u2027',2000)+"\n");_jsonPayloadSequence=SequenceFactory.Create(_jsonPayload,SegmentSize);}[Benchmark]publicvoidReadCommentWithSeparators(){varstate=newJsonReaderState(newJsonReaderOptions{CommentHandling=CommentHandling});Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_jsonPayloadSequence,isFinalBlock:true,state):newUtf8JsonReader(_jsonPayload,isFinalBlock:true,state);while(reader.Read()){}}}publicclassPerf_ValueTextEquals{privateconstintPropertyCount=100;[Params(false,true)]publicboolEscaped;[Params(false,true)]publicboolMultiSegment;privatebyte[]_dataUtf8=null!;privateReadOnlySequence<byte>_sequence;privatebyte[]_lookupUtf8=null!;[GlobalSetup]publicvoidSetup(){_lookupUtf8=Encoding.UTF8.GetBytes("property_"+PropertyCount);varbuilder=newStringBuilder("{");for(inti=0;i<PropertyCount;i++){if(i!=0){builder.Append(',');}builder.Append('"');AppendPropertyName(builder,i,Escaped);builder.Append("\":");builder.Append(i);}builder.Append('}');_dataUtf8=Encoding.UTF8.GetBytes(builder.ToString());_sequence=SequenceFactory.Create(_dataUtf8,_dataUtf8.Length/2);}[Benchmark]publicintMatchPropertyNames(){Utf8JsonReaderreader=MultiSegment?newUtf8JsonReader(_sequence):newUtf8JsonReader(_dataUtf8);intmatches=0;while(reader.Read()){if(reader.TokenType==JsonTokenType.PropertyName&&reader.ValueTextEquals(_lookupUtf8)){matches++;}}returnmatches;}privatestaticvoidAppendPropertyName(StringBuilderbuilder,intindex,boolescaped){conststringPrefix="property_";if(!escaped){builder.Append(Prefix).Append(index);return;}foreach(charcinPrefix){builder.Append("\\u").Append(((int)c).ToString("x4"));}builder.Append(index);}}internalstaticclassSequenceFactory{publicstaticReadOnlySequence<byte>Create(byte[]data,intsegmentSize){varfirst=newBufferSegment(data.AsMemory(0,Math.Min(segmentSize,data.Length)));BufferSegmentlast=first;for(intoffset=segmentSize;offset<data.Length;offset+=segmentSize){last=last.Append(data.AsMemory(offset,Math.Min(segmentSize,data.Length-offset)));}returnnewReadOnlySequence<byte>(first,0,last,last.Memory.Length);}privatesealedclassBufferSegment:ReadOnlySequenceSegment<byte>{publicBufferSegment(ReadOnlyMemory<byte>memory){Memory=memory;}publicBufferSegmentAppend(ReadOnlyMemory<byte>memory){varsegment=newBufferSegment(memory){RunningIndex=RunningIndex+Memory.Length};Next=segment;returnsegment;}}}

Note

This benchmark request was prepared with GitHub Copilot.

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.

3 participants

@jozkee@eiriktsarpalis