Skip to content

Improve Utf8Parser UInt64 decimal performance - #132663

Open
thomhurst wants to merge 3 commits into
dotnet:mainfrom
thomhurst:perf/utf8parser-uint64-decimal
Open

Improve Utf8Parser UInt64 decimal performance#132663
thomhurst wants to merge 3 commits into
dotnet:mainfrom
thomhurst:perf/utf8parser-uint64-decimal

Conversation

@thomhurst

Copy link
Copy Markdown

Improves decimal Utf8Parser.TryParse throughput for ulong inputs long enough to require overflow checks.

The new long-input path validates and parses the first 16 digits four bytes at a time, then uses the existing overflow logic for the remaining bytes. Inputs with a non-digit in the first 16 bytes fall back to the existing parser. Dispatch happens above TryParseUInt64D, preserving its leaf codegen for common shorter inputs.

Local BenchmarkDotNet results on Windows x64, Intel Core i7-12700K, .NET 10 source-built baseline/candidate, R2R disabled, 1 launch / 5 warmups / 10 iterations:

Input Before After Ratio
4 digits 1.082 ns 1.062 ns 0.98
9 digits 3.047 ns 2.981 ns 0.98
10 digits 3.554 ns 3.356 ns 0.94
19 digits 7.764 ns 7.901 ns 1.02
ulong.MaxValue 10.561 ns 6.587 ns 0.62
Overflow 10.896 ns 6.535 ns 0.60
19 digits + invalid suffix 10.126 ns 6.357 ns 0.63

Shorter cases were equivalent under the 5% Mann-Whitney threshold. No benchmark allocates.

Validation:

  • Current main CoreLib Release build: 0 warnings, 0 errors
  • 100,000 randomized ulong format/parse roundtrips
  • 100,000 randomized malformed-byte comparisons against the previous parser
  • Exhaustive repeated-byte digit validation
  • Added long leading-zero and early-invalid-suffix test cases

Copilot AI lite review requested due to automatic review settings August 22, 2026 22:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Aug 22, 2026
@azure-pipelines

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

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@thomhurst

Copy link
Copy Markdown
Author

@EgorBot -windows_x64 -windows_arm64 -linux_x64 -linux_arm64 -osx_arm64

using System.Buffers.Text;
using System.Text;
using BenchmarkDotNet.Attributes;

public class UInt64Utf8ParserBenchmarks
{
    private byte[] _source = null!;

    [ParamsAllValues]
    public InputCase Input { get; set; }

    [Params('\0', 'D', 'G')]
    public char Format { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        string text = Input switch
        {
            InputCase.Empty => "",
            InputCase.Zero => "0",
            InputCase.OneDigit => "7",
            InputCase.FourDigits => "1234",
            InputCase.NineDigits => "123456789",
            InputCase.TenDigits => "1234567890",
            InputCase.NineteenDigits => "1234567890123456789",
            InputCase.Maximum => "18446744073709551615",
            InputCase.Overflow => "18446744073709551616",
            InputCase.TwentyOneDigits => "100000000000000000000",
            InputCase.InvalidSuffix => "1234567890123456789x",
            InputCase.InvalidAt7 => "1234567x901234567890",
            InputCase.InvalidAt15 => "123456789012345x7890",
            InputCase.LeadingZeros => "00000000000000000000",
            _ => throw new InvalidOperationException(),
        };
        _source = Encoding.ASCII.GetBytes(text);
    }

    [Benchmark]
    public ulong TryParse()
    {
        bool success = Utf8Parser.TryParse(_source, out ulong value, out int consumed, Format);
        return value ^ ((ulong)(uint)consumed << 1) ^ (success ? 1UL : 0UL);
    }
}

public enum InputCase
{
    Empty,
    Zero,
    OneDigit,
    FourDigits,
    NineDigits,
    TenDigits,
    NineteenDigits,
    Maximum,
    Overflow,
    TwentyOneDigits,
    InvalidSuffix,
    InvalidAt7,
    InvalidAt15,
    LeadingZeros,
}

Keep short-input dispatch unchanged and resume scalar parsing after the last validated four-digit chunk instead of rescanning the prefix.
Copilot AI review requested due to automatic review settings August 23, 2026 07:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@thomhurst

Copy link
Copy Markdown
Author

@EgorBot -linux_amd -windows_intel -osx_arm64

using System.Buffers.Text;
using System.Text;
using BenchmarkDotNet.Attributes;

public class UInt64Utf8ParserBenchmarks
{
    private byte[] _source = null!;

    [ParamsAllValues]
    public InputCase Input { get; set; }

    [Params('\0', 'D', 'G')]
    public char Format { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        string text = Input switch
        {
            InputCase.Empty => "",
            InputCase.Zero => "0",
            InputCase.OneDigit => "7",
            InputCase.FourDigits => "1234",
            InputCase.NineDigits => "123456789",
            InputCase.TenDigits => "1234567890",
            InputCase.NineteenDigits => "1234567890123456789",
            InputCase.Maximum => "18446744073709551615",
            InputCase.Overflow => "18446744073709551616",
            InputCase.TwentyOneDigits => "100000000000000000000",
            InputCase.InvalidSuffix => "1234567890123456789x",
            InputCase.InvalidAt3 => "123x5678901234567890",
            InputCase.InvalidAt7 => "1234567x901234567890",
            InputCase.InvalidAt11 => "12345678901x34567890",
            InputCase.InvalidAt15 => "123456789012345x7890",
            InputCase.LeadingZeros => "00000000000000000000",
            _ => throw new InvalidOperationException(),
        };
        _source = Encoding.ASCII.GetBytes(text);
    }

    [Benchmark]
    public ulong TryParse()
    {
        bool success = Utf8Parser.TryParse(_source, out ulong value, out int consumed, Format);
        return value ^ ((ulong)(uint)consumed << 1) ^ (success ? 1UL : 0UL);
    }
}

public enum InputCase
{
    Empty,
    Zero,
    OneDigit,
    FourDigits,
    NineDigits,
    TenDigits,
    NineteenDigits,
    Maximum,
    Overflow,
    TwentyOneDigits,
    InvalidSuffix,
    InvalidAt3,
    InvalidAt7,
    InvalidAt11,
    InvalidAt15,
    LeadingZeros,
}

Note

Benchmark request is AI-generated with Codex.

Copilot AI review requested due to automatic review settings August 23, 2026 09:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@thomhurst

Copy link
Copy Markdown
Author

@EgorBot -linux_amd -windows_intel -osx_arm64

using System.Buffers.Text;
using System.Text;
using BenchmarkDotNet.Attributes;

public class UInt64Utf8ParserBenchmarks
{
    private byte[] _source = null!;

    [ParamsAllValues]
    public InputCase Input { get; set; }

    [Params('\0', 'D', 'G')]
    public char Format { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        string text = Input switch
        {
            InputCase.Empty => "",
            InputCase.Zero => "0",
            InputCase.OneDigit => "7",
            InputCase.FourDigits => "1234",
            InputCase.NineDigits => "123456789",
            InputCase.TenDigits => "1234567890",
            InputCase.NineteenDigits => "1234567890123456789",
            InputCase.Maximum => "18446744073709551615",
            InputCase.Overflow => "18446744073709551616",
            InputCase.TwentyOneDigits => "100000000000000000000",
            InputCase.InvalidSuffix => "1234567890123456789x",
            InputCase.InvalidAt3 => "123x5678901234567890",
            InputCase.InvalidAt7 => "1234567x901234567890",
            InputCase.InvalidAt11 => "12345678901x34567890",
            InputCase.InvalidAt15 => "123456789012345x7890",
            InputCase.LeadingZeros => "00000000000000000000",
            _ => throw new InvalidOperationException(),
        };
        _source = Encoding.ASCII.GetBytes(text);
    }

    [Benchmark]
    public ulong TryParse()
    {
        bool success = Utf8Parser.TryParse(_source, out ulong value, out int consumed, Format);
        return value ^ ((ulong)(uint)consumed << 1) ^ (success ? 1UL : 0UL);
    }
}

public enum InputCase
{
    Empty,
    Zero,
    OneDigit,
    FourDigits,
    NineDigits,
    TenDigits,
    NineteenDigits,
    Maximum,
    Overflow,
    TwentyOneDigits,
    InvalidSuffix,
    InvalidAt3,
    InvalidAt7,
    InvalidAt11,
    InvalidAt15,
    LeadingZeros,
}

Note

Benchmark request is AI-generated with Codex.

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants