Skip to content

[EXPERIMENT] System.Text.Json: Performance optimizations for hot paths#124427

Draft
artl93 wants to merge 1 commit intodotnet:mainfrom
artl93:perf/system-text-json-optimizations
Draft

[EXPERIMENT] System.Text.Json: Performance optimizations for hot paths#124427
artl93 wants to merge 1 commit intodotnet:mainfrom
artl93:perf/system-text-json-optimizations

Conversation

@artl93
Copy link
Member

@artl93 artl93 commented Feb 14, 2026

⚠️ Experimental PR — Do Not Merge

This is an experimental performance exploration PR to validate speculative optimizations with EgorBot benchmarks. Please do not merge.

Changes

  1. Reader: Inline delimiter/escape checks — Replace Delimiters.Contains() (8-byte span scan) with IsDelimiter() switch expression in 5 hot-path call sites. Same for EscapableChars.IndexOf()IsEscapableChar() (3 call sites).

  2. Writer: AggressiveInlining — Add [MethodImpl(MethodImplOptions.AggressiveInlining)] to ValidateWritingValue() and SetFlagToAddListSeparatorBeforeNextItem().

  3. Enum converter: Regex → manual char scan — Replace JsonHelpers.IntegerRegex.IsMatch() with IsIntegerLike() loop in EnumConverter.

Validation

  • All 49,965 System.Text.Json tests pass (0 failures)
  • EgorBot benchmark results pending below

Full audit report: https://gist.github.com/artl93/8f375461b1a29d48b26053b88db76058

- Replace IntegerRegex with manual char scanning in enum parsing for
  faster integer detection in TryParseEnumFromString
- Add AggressiveInlining to ValidateWritingValue and
  SetFlagToAddListSeparatorBeforeNextItem in Utf8JsonWriter (called on
  every value write)
- Replace Delimiters.Contains() linear search with inline IsDelimiter()
  switch in number parsing hot paths (Utf8JsonReader and MultiSegment)
- Replace EscapableChars.IndexOf() linear search with inline
  IsEscapableChar() switch in string escape validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 14, 2026 19:33
@artl93 artl93 added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Feb 14, 2026
@dotnet-policy-service
Copy link
Contributor

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

@artl93
Copy link
Member Author

artl93 commented Feb 14, 2026

@EgorBot -amd -arm

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text.Json;
using System.Text.Json.Serialization;

BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);

public enum Color { Red, Green, Blue, Yellow, Cyan, Magenta, White, Black }

public class SmallPoco
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public bool Active { get; set; }
    public double Score { get; set; }
}

[MemoryDiagnoser]
public class Bench
{
    private byte[] _smallJson = default!;
    private byte[] _nestedJson = default!;
    private byte[] _numberHeavyJson = default!;
    private JsonSerializerOptions _enumOptions = default!;

    [GlobalSetup]
    public void Setup()
    {
        _smallJson = JsonSerializer.SerializeToUtf8Bytes(
            new SmallPoco { Id = 42, Name = "test", Active = true, Score = 3.14 });
        _nestedJson = JsonSerializer.SerializeToUtf8Bytes(
            new { a = new { b = 1, c = "hello\nworld" }, d = new[] { 1, 2, 3 }, e = true, f = 0.0 });
        _numberHeavyJson = System.Text.Encoding.UTF8.GetBytes("[0, 0.1, 0.123, 1, 12, 123, 1234, 12345, 0.999, 1.0e10]");
        _enumOptions = new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } };
    }

    [Benchmark]
    public void Reader_SmallPoco()
    {
        var reader = new Utf8JsonReader(_smallJson);
        while (reader.Read()) { }
    }

    [Benchmark]
    public void Reader_NestedWithEscapes()
    {
        var reader = new Utf8JsonReader(_nestedJson);
        while (reader.Read()) { }
    }

    [Benchmark]
    public void Reader_NumberHeavy()
    {
        var reader = new Utf8JsonReader(_numberHeavyJson);
        while (reader.Read()) { }
    }

    [Benchmark]
    public byte[] Writer_SmallPoco()
    {
        return JsonSerializer.SerializeToUtf8Bytes(
            new SmallPoco { Id = 42, Name = "test", Active = true, Score = 3.14 });
    }

    [Benchmark]
    public Color Deserialize_Enum_String()
    {
        return JsonSerializer.Deserialize<Color>("\"Blue\"", _enumOptions);
    }

    [Benchmark]
    public SmallPoco Deserialize_SmallPoco()
    {
        return JsonSerializer.Deserialize<SmallPoco>(_smallJson)!;
    }
}

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This experimental PR explores performance optimizations in System.Text.Json hot paths through three targeted changes: replacing span-based character lookups with switch expressions, adding aggressive inlining hints to frequently-called writer methods, and substituting regex validation with manual character scanning in the enum converter.

Changes:

  • Reader optimizations: Replace Delimiters.Contains() and EscapableChars.IndexOf() linear scans with IsDelimiter() and IsEscapableChar() switch expressions at 8 call sites
  • Writer optimizations: Add AggressiveInlining to ValidateWritingValue() and SetFlagToAddListSeparatorBeforeNextItem()
  • Enum converter: Replace JsonHelpers.IntegerRegex.IsMatch() with manual IsIntegerLike() character loop

Reviewed changes

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

Show a summary per file
File Description
src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs Adds IsDelimiter() and IsEscapableChar() switch expression helpers with aggressive inlining to replace span linear searches
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs Replaces 3 Delimiters.Contains() and 1 EscapableChars.IndexOf() calls with new switch expression helpers
src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs Replaces 4 Delimiters.Contains() and 2 EscapableChars.IndexOf() calls with new switch expression helpers
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs Adds AggressiveInlining attribute to SetFlagToAddListSeparatorBeforeNextItem()
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs Adds AggressiveInlining attribute to ValidateWritingValue()
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs Replaces regex-based integer detection with manual IsIntegerLike() character loop

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

Labels

area-System.Text.Json NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments