Skip to content

Improve validated raw JSON writing performance on ARM64#130514

Closed
EgorBo wants to merge 7 commits into
dotnet:mainfrom
EgorBo:egor/arm64-json-raw-performance
Closed

Improve validated raw JSON writing performance on ARM64#130514
EgorBo wants to merge 7 commits into
dotnet:mainfrom
EgorBo:egor/arm64-json-raw-performance

Conversation

@EgorBo

@EgorBo EgorBo commented Jul 10, 2026

Copy link
Copy Markdown
Member

Contributes to #126679.

Just a bunch of AI generated changes to see if they help with the issue

Use CompareTest for nonzero SIMD masks on ARM64 and avoid temporary UTF-8 buffers when validated raw JSON fits in the writer buffer.

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

Copilot-Session: 836935ce-8491-4faf-bf6c-6d7e8e6ab92c
Copilot AI review requested due to automatic review settings July 10, 2026 18:38
@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.

@EgorBo

EgorBo commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -azure_arm -azure_amd -arm -profiler

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

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

[MemoryDiagnoser]
public class Benchmarks
{
    // ── TokenSerialization fields ────────────────────────────────────────────
    private List<object> _tokenObjects;
    [ThreadStatic] static Utf8JsonWriter t_writer;
    [ThreadStatic] static MemoryStream t_stream;

    [GlobalSetup]
    public void Setup()
    {
        // TokenSerialization
        _tokenObjects = new List<object>(200);
        for (int i = 0; i < 200; i++)
        {
            if (i % 3 == 0)
                _tokenObjects.Add(GenerateRecordJson(1));
            else
                _tokenObjects.Add(new Dictionary<string, object>
                {
                    ["seq"] = i,
                    ["label"] = $"item_{i}",
                    ["blob"] = new byte[100]
                });
        }
    }

    private static string GenerateRecordJson(int targetSizeKb = 150)
    {
        var sb = new StringBuilder(targetSizeKb * 1024 + 512);
        sb.Append("{");
        sb.Append("\"TypeName\":\"product\",");
        sb.Append("\"CategoryCode\":1,");
        sb.Append("\"Label\":\"Product\",");
        sb.Append("\"IsAction\":false,");
        sb.Append("\"IsActionMember\":false,");
        sb.Append("\"IsTrackingEnabled\":true,");
        sb.Append("\"IsAvailableLocal\":true,");
        sb.Append("\"IsChildRecord\":false,");
        sb.Append("\"IsLinksEnabled\":true,");
        sb.Append("\"IsCustomRecord\":false,");
        sb.Append("\"PrimaryKeyField\":\"productid\",");
        sb.Append("\"PrimaryLabelField\":\"title\",");
        sb.Append("\"Fields\":[");
        int targetBytes = targetSizeKb * 1024;
        int fieldIndex = 0;
        bool firstField = true;
        while (sb.Length < targetBytes - 512)
        {
            if (!firstField) sb.Append(",");
            firstField = false;
            sb.Append("{");
            sb.Append($"\"TypeName\":\"field_{fieldIndex}\",");
            sb.Append($"\"InternalName\":\"Field_{fieldIndex}\",");
            sb.Append($"\"FieldType\":\"String\",");
            sb.Append($"\"Label\":\"Field {fieldIndex}\",");
            sb.Append($"\"MaxSize\":100,");
            sb.Append($"\"IsReadable\":true,");
            sb.Append($"\"IsCreatable\":true,");
            sb.Append($"\"IsUpdatable\":true,");
            sb.Append($"\"IsTrackingEnabled\":false,");
            sb.Append($"\"IsPrimaryKey\":false,");
            sb.Append($"\"IsVirtual\":false,");
            sb.Append($"\"Requirement\":\"None\"");
            sb.Append("}");
            fieldIndex++;
        }
        sb.Append("]");
        sb.Append("}");
        return sb.ToString();
    }

    [Benchmark]
    public void TokenSerialization()
    {
        var stream = t_stream ??= new MemoryStream(64 * 1024);
        stream.Position = 0;
        stream.SetLength(0);
        var writer = t_writer;
        if (writer == null)
        {
            writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = true });
            t_writer = writer;
        }
        else
            writer.Reset(stream);
        writer.WriteStartObject();
        writer.WriteStartArray("Catalog");
        foreach (var token in _tokenObjects)
        {
            if (token is string strToken)
            {
                if (!string.IsNullOrEmpty(strToken))
                    writer.WriteRawValue(strToken);
            }
            else if (token is Dictionary<string, object> dictToken)
            {
                writer.WriteStartObject();
                foreach (var kvp in dictToken)
                {
                    writer.WritePropertyName(kvp.Key);
                    JsonSerializer.Serialize(writer, kvp.Value);
                }
                writer.WriteEndObject();
            }
        }
        writer.WriteEndArray();
        writer.WriteEndObject();
        writer.Flush();
        if (stream.Length == 0) throw new Exception("unreachable");
    }
}

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.

Pull request overview

This PR optimizes two ARM64-heavy hot paths: writing validated raw JSON values in System.Text.Json (by avoiding a temporary UTF-8 buffer/copy when the writer already has enough free capacity) and JIT lowering for a common ARM64 vector pattern (emitting cmtst via AdvSimd.CompareTest instead of cmeq+mvn).

Changes:

  • Utf8JsonWriter: transcode UTF-16 raw JSON directly into the writer buffer when capacity allows, validate before committing, and factor validation into a non-inlined helper.
  • RyuJIT ARM64 lowering: recognize Not(CompareEqual(value, Zero)) for supported integer vector element types and replace it with CompareTest(value, value).

Reviewed changes

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

File Description
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Raw.cs Adds a fast-path to transcode directly into the existing writer buffer and extracts raw-value validation to a helper to reduce inlining/code size impact.
src/coreclr/jit/lowerarmarch.cpp Adds an ARM64 lowering transform to produce cmtst for ~(value == 0) vector patterns over supported integer element types.

Comment thread src/coreclr/jit/lowerarmarch.cpp Outdated

case GT_NOT:
{
// ~Vector.Equals(value, Zero) is equivalent to AdvSimd.CompareTest(value, value) for integer elements.
Revert the System.Text.Json writer changes so the benchmark isolates the ARM64 CompareTest lowering optimization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 368c0f32-0d14-4790-817b-dd954b9c454b
@EgorBo

EgorBo commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -azure_arm

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

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

[MemoryDiagnoser]
public class Benchmarks
{
    // ── TokenSerialization fields ────────────────────────────────────────────
    private List<object> _tokenObjects;
    [ThreadStatic] static Utf8JsonWriter t_writer;
    [ThreadStatic] static MemoryStream t_stream;

    [GlobalSetup]
    public void Setup()
    {
        // TokenSerialization
        _tokenObjects = new List<object>(200);
        for (int i = 0; i < 200; i++)
        {
            if (i % 3 == 0)
                _tokenObjects.Add(GenerateRecordJson(1));
            else
                _tokenObjects.Add(new Dictionary<string, object>
                {
                    ["seq"] = i,
                    ["label"] = $"item_{i}",
                    ["blob"] = new byte[100]
                });
        }
    }

    private static string GenerateRecordJson(int targetSizeKb = 150)
    {
        var sb = new StringBuilder(targetSizeKb * 1024 + 512);
        sb.Append("{");
        sb.Append("\"TypeName\":\"product\",");
        sb.Append("\"CategoryCode\":1,");
        sb.Append("\"Label\":\"Product\",");
        sb.Append("\"IsAction\":false,");
        sb.Append("\"IsActionMember\":false,");
        sb.Append("\"IsTrackingEnabled\":true,");
        sb.Append("\"IsAvailableLocal\":true,");
        sb.Append("\"IsChildRecord\":false,");
        sb.Append("\"IsLinksEnabled\":true,");
        sb.Append("\"IsCustomRecord\":false,");
        sb.Append("\"PrimaryKeyField\":\"productid\",");
        sb.Append("\"PrimaryLabelField\":\"title\",");
        sb.Append("\"Fields\":[");
        int targetBytes = targetSizeKb * 1024;
        int fieldIndex = 0;
        bool firstField = true;
        while (sb.Length < targetBytes - 512)
        {
            if (!firstField) sb.Append(",");
            firstField = false;
            sb.Append("{");
            sb.Append($"\"TypeName\":\"field_{fieldIndex}\",");
            sb.Append($"\"InternalName\":\"Field_{fieldIndex}\",");
            sb.Append($"\"FieldType\":\"String\",");
            sb.Append($"\"Label\":\"Field {fieldIndex}\",");
            sb.Append($"\"MaxSize\":100,");
            sb.Append($"\"IsReadable\":true,");
            sb.Append($"\"IsCreatable\":true,");
            sb.Append($"\"IsUpdatable\":true,");
            sb.Append($"\"IsTrackingEnabled\":false,");
            sb.Append($"\"IsPrimaryKey\":false,");
            sb.Append($"\"IsVirtual\":false,");
            sb.Append($"\"Requirement\":\"None\"");
            sb.Append("}");
            fieldIndex++;
        }
        sb.Append("]");
        sb.Append("}");
        return sb.ToString();
    }

    [Benchmark]
    public void TokenSerialization()
    {
        var stream = t_stream ??= new MemoryStream(64 * 1024);
        stream.Position = 0;
        stream.SetLength(0);
        var writer = t_writer;
        if (writer == null)
        {
            writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = true });
            t_writer = writer;
        }
        else
            writer.Reset(stream);
        writer.WriteStartObject();
        writer.WriteStartArray("Catalog");
        foreach (var token in _tokenObjects)
        {
            if (token is string strToken)
            {
                if (!string.IsNullOrEmpty(strToken))
                    writer.WriteRawValue(strToken);
            }
            else if (token is Dictionary<string, object> dictToken)
            {
                writer.WriteStartObject();
                foreach (var kvp in dictToken)
                {
                    writer.WritePropertyName(kvp.Key);
                    JsonSerializer.Serialize(writer, kvp.Value);
                }
                writer.WriteEndObject();
            }
        }
        writer.WriteEndArray();
        writer.WriteEndObject();
        writer.Flush();
        if (stream.Length == 0) throw new Exception("unreachable");
    }
}

@EgorBo

EgorBo commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -azure_arm

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

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

[MemoryDiagnoser]
public class Benchmarks
{
    // ── TokenSerialization fields ────────────────────────────────────────────
    private List<object> _tokenObjects;
    [ThreadStatic] static Utf8JsonWriter t_writer;
    [ThreadStatic] static MemoryStream t_stream;

    [GlobalSetup]
    public void Setup()
    {
        // TokenSerialization
        _tokenObjects = new List<object>(200);
        for (int i = 0; i < 200; i++)
        {
            if (i % 3 == 0)
                _tokenObjects.Add(GenerateRecordJson(1));
            else
                _tokenObjects.Add(new Dictionary<string, object>
                {
                    ["seq"] = i,
                    ["label"] = $"item_{i}",
                    ["blob"] = new byte[100]
                });
        }
    }

    private static string GenerateRecordJson(int targetSizeKb = 150)
    {
        var sb = new StringBuilder(targetSizeKb * 1024 + 512);
        sb.Append("{");
        sb.Append("\"TypeName\":\"product\",");
        sb.Append("\"CategoryCode\":1,");
        sb.Append("\"Label\":\"Product\",");
        sb.Append("\"IsAction\":false,");
        sb.Append("\"IsActionMember\":false,");
        sb.Append("\"IsTrackingEnabled\":true,");
        sb.Append("\"IsAvailableLocal\":true,");
        sb.Append("\"IsChildRecord\":false,");
        sb.Append("\"IsLinksEnabled\":true,");
        sb.Append("\"IsCustomRecord\":false,");
        sb.Append("\"PrimaryKeyField\":\"productid\",");
        sb.Append("\"PrimaryLabelField\":\"title\",");
        sb.Append("\"Fields\":[");
        int targetBytes = targetSizeKb * 1024;
        int fieldIndex = 0;
        bool firstField = true;
        while (sb.Length < targetBytes - 512)
        {
            if (!firstField) sb.Append(",");
            firstField = false;
            sb.Append("{");
            sb.Append($"\"TypeName\":\"field_{fieldIndex}\",");
            sb.Append($"\"InternalName\":\"Field_{fieldIndex}\",");
            sb.Append($"\"FieldType\":\"String\",");
            sb.Append($"\"Label\":\"Field {fieldIndex}\",");
            sb.Append($"\"MaxSize\":100,");
            sb.Append($"\"IsReadable\":true,");
            sb.Append($"\"IsCreatable\":true,");
            sb.Append($"\"IsUpdatable\":true,");
            sb.Append($"\"IsTrackingEnabled\":false,");
            sb.Append($"\"IsPrimaryKey\":false,");
            sb.Append($"\"IsVirtual\":false,");
            sb.Append($"\"Requirement\":\"None\"");
            sb.Append("}");
            fieldIndex++;
        }
        sb.Append("]");
        sb.Append("}");
        return sb.ToString();
    }

    [Benchmark]
    public void TokenSerialization()
    {
        var stream = t_stream ??= new MemoryStream(64 * 1024);
        stream.Position = 0;
        stream.SetLength(0);
        var writer = t_writer;
        if (writer == null)
        {
            writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = true });
            t_writer = writer;
        }
        else
            writer.Reset(stream);
        writer.WriteStartObject();
        writer.WriteStartArray("Catalog");
        foreach (var token in _tokenObjects)
        {
            if (token is string strToken)
            {
                if (!string.IsNullOrEmpty(strToken))
                    writer.WriteRawValue(strToken);
            }
            else if (token is Dictionary<string, object> dictToken)
            {
                writer.WriteStartObject();
                foreach (var kvp in dictToken)
                {
                    writer.WritePropertyName(kvp.Key);
                    JsonSerializer.Serialize(writer, kvp.Value);
                }
                writer.WriteEndObject();
            }
        }
        writer.WriteEndArray();
        writer.WriteEndObject();
        writer.Flush();
        if (stream.Length == 0) throw new Exception("unreachable");
    }
}

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.

Pull request overview

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

Restore the System.Text.Json writer optimization and remove the ARM64 JIT lowering change so the benchmark isolates the managed implementation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 368c0f32-0d14-4790-817b-dd954b9c454b
Copilot AI review requested due to automatic review settings July 10, 2026 21:41

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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings July 10, 2026 22:32

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.

Pull request overview

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

Copilot AI review requested due to automatic review settings July 10, 2026 22:36

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.

Pull request overview

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

Copilot AI review requested due to automatic review settings July 10, 2026 23:04
@EgorBo

EgorBo commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -azure_arm -aws_arm -amd

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

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

[MemoryDiagnoser]
public class Benchmarks
{
    // ── TokenSerialization fields ────────────────────────────────────────────
    private List<object> _tokenObjects;
    [ThreadStatic] static Utf8JsonWriter t_writer;
    [ThreadStatic] static MemoryStream t_stream;

    [GlobalSetup]
    public void Setup()
    {
        // TokenSerialization
        _tokenObjects = new List<object>(200);
        for (int i = 0; i < 200; i++)
        {
            if (i % 3 == 0)
                _tokenObjects.Add(GenerateRecordJson(1));
            else
                _tokenObjects.Add(new Dictionary<string, object>
                {
                    ["seq"] = i,
                    ["label"] = $"item_{i}",
                    ["blob"] = new byte[100]
                });
        }
    }

    private static string GenerateRecordJson(int targetSizeKb = 150)
    {
        var sb = new StringBuilder(targetSizeKb * 1024 + 512);
        sb.Append("{");
        sb.Append("\"TypeName\":\"product\",");
        sb.Append("\"CategoryCode\":1,");
        sb.Append("\"Label\":\"Product\",");
        sb.Append("\"IsAction\":false,");
        sb.Append("\"IsActionMember\":false,");
        sb.Append("\"IsTrackingEnabled\":true,");
        sb.Append("\"IsAvailableLocal\":true,");
        sb.Append("\"IsChildRecord\":false,");
        sb.Append("\"IsLinksEnabled\":true,");
        sb.Append("\"IsCustomRecord\":false,");
        sb.Append("\"PrimaryKeyField\":\"productid\",");
        sb.Append("\"PrimaryLabelField\":\"title\",");
        sb.Append("\"Fields\":[");
        int targetBytes = targetSizeKb * 1024;
        int fieldIndex = 0;
        bool firstField = true;
        while (sb.Length < targetBytes - 512)
        {
            if (!firstField) sb.Append(",");
            firstField = false;
            sb.Append("{");
            sb.Append($"\"TypeName\":\"field_{fieldIndex}\",");
            sb.Append($"\"InternalName\":\"Field_{fieldIndex}\",");
            sb.Append($"\"FieldType\":\"String\",");
            sb.Append($"\"Label\":\"Field {fieldIndex}\",");
            sb.Append($"\"MaxSize\":100,");
            sb.Append($"\"IsReadable\":true,");
            sb.Append($"\"IsCreatable\":true,");
            sb.Append($"\"IsUpdatable\":true,");
            sb.Append($"\"IsTrackingEnabled\":false,");
            sb.Append($"\"IsPrimaryKey\":false,");
            sb.Append($"\"IsVirtual\":false,");
            sb.Append($"\"Requirement\":\"None\"");
            sb.Append("}");
            fieldIndex++;
        }
        sb.Append("]");
        sb.Append("}");
        return sb.ToString();
    }

    [Benchmark]
    public void TokenSerialization()
    {
        var stream = t_stream ??= new MemoryStream(64 * 1024);
        stream.Position = 0;
        stream.SetLength(0);
        var writer = t_writer;
        if (writer == null)
        {
            writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = true });
            t_writer = writer;
        }
        else
            writer.Reset(stream);
        writer.WriteStartObject();
        writer.WriteStartArray("Catalog");
        foreach (var token in _tokenObjects)
        {
            if (token is string strToken)
            {
                if (!string.IsNullOrEmpty(strToken))
                    writer.WriteRawValue(strToken);
            }
            else if (token is Dictionary<string, object> dictToken)
            {
                writer.WriteStartObject();
                foreach (var kvp in dictToken)
                {
                    writer.WritePropertyName(kvp.Key);
                    JsonSerializer.Serialize(writer, kvp.Value);
                }
                writer.WriteEndObject();
            }
        }
        writer.WriteEndArray();
        writer.WriteEndObject();
        writer.Flush();
        if (stream.Length == 0) throw new Exception("unreachable");
    }
}

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.

Pull request overview

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

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 10, 2026 23:14

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.

Pull request overview

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

@EgorBo

EgorBo commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -azure_arm -aws_graviton5 -amd

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

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

[MemoryDiagnoser]
public class Benchmarks
{
    // ── TokenSerialization fields ────────────────────────────────────────────
    private List<object> _tokenObjects;
    [ThreadStatic] static Utf8JsonWriter t_writer;
    [ThreadStatic] static MemoryStream t_stream;

    [GlobalSetup]
    public void Setup()
    {
        // TokenSerialization
        _tokenObjects = new List<object>(200);
        for (int i = 0; i < 200; i++)
        {
            if (i % 3 == 0)
                _tokenObjects.Add(GenerateRecordJson(1));
            else
                _tokenObjects.Add(new Dictionary<string, object>
                {
                    ["seq"] = i,
                    ["label"] = $"item_{i}",
                    ["blob"] = new byte[100]
                });
        }
    }

    private static string GenerateRecordJson(int targetSizeKb = 150)
    {
        var sb = new StringBuilder(targetSizeKb * 1024 + 512);
        sb.Append("{");
        sb.Append("\"TypeName\":\"product\",");
        sb.Append("\"CategoryCode\":1,");
        sb.Append("\"Label\":\"Product\",");
        sb.Append("\"IsAction\":false,");
        sb.Append("\"IsActionMember\":false,");
        sb.Append("\"IsTrackingEnabled\":true,");
        sb.Append("\"IsAvailableLocal\":true,");
        sb.Append("\"IsChildRecord\":false,");
        sb.Append("\"IsLinksEnabled\":true,");
        sb.Append("\"IsCustomRecord\":false,");
        sb.Append("\"PrimaryKeyField\":\"productid\",");
        sb.Append("\"PrimaryLabelField\":\"title\",");
        sb.Append("\"Fields\":[");
        int targetBytes = targetSizeKb * 1024;
        int fieldIndex = 0;
        bool firstField = true;
        while (sb.Length < targetBytes - 512)
        {
            if (!firstField) sb.Append(",");
            firstField = false;
            sb.Append("{");
            sb.Append($"\"TypeName\":\"field_{fieldIndex}\",");
            sb.Append($"\"InternalName\":\"Field_{fieldIndex}\",");
            sb.Append($"\"FieldType\":\"String\",");
            sb.Append($"\"Label\":\"Field {fieldIndex}\",");
            sb.Append($"\"MaxSize\":100,");
            sb.Append($"\"IsReadable\":true,");
            sb.Append($"\"IsCreatable\":true,");
            sb.Append($"\"IsUpdatable\":true,");
            sb.Append($"\"IsTrackingEnabled\":false,");
            sb.Append($"\"IsPrimaryKey\":false,");
            sb.Append($"\"IsVirtual\":false,");
            sb.Append($"\"Requirement\":\"None\"");
            sb.Append("}");
            fieldIndex++;
        }
        sb.Append("]");
        sb.Append("}");
        return sb.ToString();
    }

    [Benchmark]
    public void TokenSerialization()
    {
        var stream = t_stream ??= new MemoryStream(64 * 1024);
        stream.Position = 0;
        stream.SetLength(0);
        var writer = t_writer;
        if (writer == null)
        {
            writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = true });
            t_writer = writer;
        }
        else
            writer.Reset(stream);
        writer.WriteStartObject();
        writer.WriteStartArray("Catalog");
        foreach (var token in _tokenObjects)
        {
            if (token is string strToken)
            {
                if (!string.IsNullOrEmpty(strToken))
                    writer.WriteRawValue(strToken);
            }
            else if (token is Dictionary<string, object> dictToken)
            {
                writer.WriteStartObject();
                foreach (var kvp in dictToken)
                {
                    writer.WritePropertyName(kvp.Key);
                    JsonSerializer.Serialize(writer, kvp.Value);
                }
                writer.WriteEndObject();
            }
        }
        writer.WriteEndArray();
        writer.WriteEndObject();
        writer.Flush();
        if (stream.Length == 0) throw new Exception("unreachable");
    }
}

@EgorBo

EgorBo commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -azure_arm -aws_arm -amd

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

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

[MemoryDiagnoser]
public class Benchmarks
{
    // ── TokenSerialization fields ────────────────────────────────────────────
    private List<object> _tokenObjects;
    [ThreadStatic] static Utf8JsonWriter t_writer;
    [ThreadStatic] static MemoryStream t_stream;

    [GlobalSetup]
    public void Setup()
    {
        // TokenSerialization
        _tokenObjects = new List<object>(200);
        for (int i = 0; i < 200; i++)
        {
            if (i % 3 == 0)
                _tokenObjects.Add(GenerateRecordJson(1));
            else
                _tokenObjects.Add(new Dictionary<string, object>
                {
                    ["seq"] = i,
                    ["label"] = $"item_{i}",
                    ["blob"] = new byte[100]
                });
        }
    }

    private static string GenerateRecordJson(int targetSizeKb = 150)
    {
        var sb = new StringBuilder(targetSizeKb * 1024 + 512);
        sb.Append("{");
        sb.Append("\"TypeName\":\"product\",");
        sb.Append("\"CategoryCode\":1,");
        sb.Append("\"Label\":\"Product\",");
        sb.Append("\"IsAction\":false,");
        sb.Append("\"IsActionMember\":false,");
        sb.Append("\"IsTrackingEnabled\":true,");
        sb.Append("\"IsAvailableLocal\":true,");
        sb.Append("\"IsChildRecord\":false,");
        sb.Append("\"IsLinksEnabled\":true,");
        sb.Append("\"IsCustomRecord\":false,");
        sb.Append("\"PrimaryKeyField\":\"productid\",");
        sb.Append("\"PrimaryLabelField\":\"title\",");
        sb.Append("\"Fields\":[");
        int targetBytes = targetSizeKb * 1024;
        int fieldIndex = 0;
        bool firstField = true;
        while (sb.Length < targetBytes - 512)
        {
            if (!firstField) sb.Append(",");
            firstField = false;
            sb.Append("{");
            sb.Append($"\"TypeName\":\"field_{fieldIndex}\",");
            sb.Append($"\"InternalName\":\"Field_{fieldIndex}\",");
            sb.Append($"\"FieldType\":\"String\",");
            sb.Append($"\"Label\":\"Field {fieldIndex}\",");
            sb.Append($"\"MaxSize\":100,");
            sb.Append($"\"IsReadable\":true,");
            sb.Append($"\"IsCreatable\":true,");
            sb.Append($"\"IsUpdatable\":true,");
            sb.Append($"\"IsTrackingEnabled\":false,");
            sb.Append($"\"IsPrimaryKey\":false,");
            sb.Append($"\"IsVirtual\":false,");
            sb.Append($"\"Requirement\":\"None\"");
            sb.Append("}");
            fieldIndex++;
        }
        sb.Append("]");
        sb.Append("}");
        return sb.ToString();
    }

    [Benchmark]
    public void TokenSerialization()
    {
        var stream = t_stream ??= new MemoryStream(64 * 1024);
        stream.Position = 0;
        stream.SetLength(0);
        var writer = t_writer;
        if (writer == null)
        {
            writer = new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = true });
            t_writer = writer;
        }
        else
            writer.Reset(stream);
        writer.WriteStartObject();
        writer.WriteStartArray("Catalog");
        foreach (var token in _tokenObjects)
        {
            if (token is string strToken)
            {
                if (!string.IsNullOrEmpty(strToken))
                    writer.WriteRawValue(strToken);
            }
            else if (token is Dictionary<string, object> dictToken)
            {
                writer.WriteStartObject();
                foreach (var kvp in dictToken)
                {
                    writer.WritePropertyName(kvp.Key);
                    JsonSerializer.Serialize(writer, kvp.Value);
                }
                writer.WriteEndObject();
            }
        }
        writer.WriteEndArray();
        writer.WriteEndObject();
        writer.Flush();
        if (stream.Length == 0) throw new Exception("unreachable");
    }
}

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

2 participants