Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[efficiency-improver] Make Jsonite JsonWriter's type-dispatch table static (avoid per-call Dictionary+closures)

Open
#11,503 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
15/100
Issue type
Refactor
Clarity
Clearly specified
Activity status
Stale
Tech stack
csharp

Research direction

The change is in src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Jsonite/JsonWriter.cs, with coverage in test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsoniteTests.cs. Review the existing branch and patch, then run the listed build and server-mode tests; done means the build passes and serialization behavior remains unchanged without per-call dispatch-table allocation.

Written by the indexing model from the issue text.

Description

agentic-workflows area/performance type/automation

[!TIP]
Your pull request is ready to create! 🎉 ✅

Everything is OK—the changes have been pushed to branch efficiency/jsonwriter-static-dispatch-table-634c00ef659ffb9d. Please review the changes, including any protected files, before creating the pull request.

Create the pull request

The original pull request description is below.


Goal and rationale

Jsonite.JsonWriter — the JSON serializer used on the net462/netstandard2.0 server-mode code path — rebuilt an 18-entry Dictionary<Type, Action<object>>, with 18 closures each capturing the per-call TextWriter, inside its constructor on every single Serialize() call. This code runs on every RPC message and test-node update sent by a server-mode host running on .NET Framework (e.g. classic MSTest test projects talking JSON-RPC to an IDE or dotnet test), so the wasted allocation scales with test count.

Focus area

Code-Level Efficiency — redundant per-call allocation of a dispatch table that has no per-instance state and can be built once.

Approach

Hoisted the dispatch table to a private static readonly Dictionary<Type, Action<JsonWriter, object>>, using delegates that take the owning JsonWriter as an explicit first parameter (instead of capturing writer/this in a closure per instance). The instance-scoped WriteString calls now go through w.WriteString(...). No behavior change: the table is populated once per process and shared across all JsonWriter instances/calls.

Energy efficiency evidence

Proxy metrics used: wall-clock execution time and GC.GetAllocatedBytesForCurrentThread() (CPU time and memory allocation are both direct proxies for energy: fewer CPU cycles and less GC/DRAM churn per call).

Benchmarked using the actual repository source (Jsonite/*.cs copied into a standalone console harness, !NETCOREAPP forced so the changed code path runs), serializing a small mix of representative values (string, int, double, bool, Guid, DateTime) 200,000 times:

Old (per-call dictionary+closures) New (static dispatch table) Improvement
Time 464 ms 141 ms ~3.3x faster
Allocated 740,000 KB (740 MB) 73,000 KB (73 MB) ~90% less

I also verified serialization output is byte-for-byte identical before/after for DateTimeOffset, an anonymous object, an int[] array, a string with escapes, a long, and a float, using the same standalone harness.

Green Software Foundation context

  • Energy Proportionality: the fix removes work that scaled with call count but had zero per-call variability in its result — the dispatch table is process-invariant, so building it once instead of once-per-call makes resource use proportional to actual demand (unique types encountered), not call volume.
  • Hardware Efficiency: fewer heap allocations mean less GC pressure and better cache locality per call, reducing CPU cycles per RPC message.

Trade-offs

None of note. Delegates now take an extra JsonWriter parameter instead of closing over instance fields, which is a standard pattern for static dispatch tables and does not reduce readability — the intent ("static, allocation-free per-type dispatch") is documented in a comment above the field.

Reproducibility

# Build to confirm the netstandard2.0/net462-only code path still compiles:
./build.sh
.dotnet/dotnet build src/Platform/Microsoft.Testing.Platform -c Debug -f netstandard2.0 --no-restore

# Run the .NET-hosted server-mode tests (System.Text.Json path, unaffected but confirms no regression):
.dotnet/dotnet build test/UnitTests/Microsoft.Testing.Platform.UnitTests -c Debug -f net8.0 --no-restore
artifacts/bin/Microsoft.Testing.Platform.UnitTests/Debug/net8.0/Microsoft.Testing.Platform.UnitTests --filter "FullyQualifiedName~ServerMode"

The Jsonite-specific test file (test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsoniteTests.cs) is net462-only and only compiles on Windows CI (net462 is conditional on $(OS) == 'Windows_NT' and this sandbox is Linux), so it could not be run directly here. I validated correctness by copying the actual Jsonite/*.cs sources (old vs. new) into a standalone console harness with !NETCOREAPP forced, compiling and running both versions, and diffing their output for representative values — all outputs matched exactly.

Test Status

  • ./build.sh (full repo restore + build, all target frameworks including netstandard2.0/net462): 0 warnings, 0 errors.
  • dotnet format whitespace TestFx.slnx --verify-no-changes --include <file>: clean (after restoring the file's UTF-8 BOM per repo convention).
  • Microsoft.Testing.Platform.UnitTests (net8.0, full suite): 2564 passed, 0 failed, 21 skipped (pre-existing skips).
  • Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (net8.0, full suite): 105 passed, 0 failed, 2 skipped (pre-existing skips).
  • JsoniteTests.cs (net462-only, Windows-only in CI): not runnable in this Linux sandbox; behavior equivalence independently verified via the standalone harness described above.

[!NOTE]
GitHub Actions is not permitted to create or approve pull requests in this repository.

The changes have been pushed to branch efficiency/jsonwriter-static-dispatch-table-634c00ef659ffb9d and are ready to review.

To fix the permissions issue, go to Settings → Actions → General and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ

Show patch preview (34 of 131 lines)
From f6c7170a408f66a3ed94a21cf97c5ddd36aae9d3 Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: acf0a077f37c1a4c23500f05fb6f90c526694c3f
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Thu, 24 Sep 2026 22:03:58 +0000
Subject: [PATCH] Make Jsonite JsonWriter's type-dispatch table static

Jsonite.JsonWriter (the JSON serializer used on the net462/netstandard2.0
server-mode code path) rebuilt an 18-entry Dictionary<Type, Action<object>>,
with 18 closures each capturing the per-call TextWriter, inside its
constructor on every single Serialize() call. This runs on every RPC message
and test-node update sent by a server-mode host running on .NET Framework.

Hoist the dispatch table to a static readonly field keyed by delegates that
take the owning JsonWriter as an explicit first parameter, so no per-call
allocation or closure capture is needed. Serialization output is unchanged
(verified via a standalone harness compiling the actual repo source with the
!NETCOREAPP path forced, comparing old vs. new output byte-for-byte for
strings, numbers, Guid, DateTime(Offset), arrays, and objects).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../JsonRpc/Json/Jsonite/JsonWriter.cs        | 65 ++++++++++---------
 1 file changed, 36 insertions(+), 29 deletions(-)

diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Jsonite/JsonWriter.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Jsonite/JsonWriter.cs
index 6a77ebc..f5bed06 100644
--- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Jsonite/JsonWriter.cs
+++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Jsonite/JsonWriter.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
 // Copyright(c) 2016, Alexa
... (truncated)

[!WARNING]

Firewall blocked 94 domains

The following domains were blocked by the firewall during workflow execution:

  • 0t3vsblobprodcus362.vsblob.vsassets.io
  • 11vvsblobprodcus336.vsblob.vsassets.io
  • 1javsblobprodcus364.vsblob.vsassets.io
  • 1k9vsblobprodcus379.vsblob.vsassets.io
  • 1oavsblobprodcus350.vsblob.vsassets.io
  • 1p7vsblobprodcus324.vsblob.vsassets.io
  • 1s1vsblobprodcus386.vsblob.vsassets.io
  • 2kmvsblobprodcus39.vsblob.vsassets.io
  • 2zrvsblobprodcus388.vsblob.vsassets.io
  • 37bvsblobprodcus311.vsblob.vsassets.io
  • 37cvsblobprodcus359.vsblob.vsassets.io
  • 4m6vsblobprodcus384.vsblob.vsassets.io
  • 4myvsblobprodcus32.vsblob.vsassets.io
  • 4vyvsblobprodcus361.vsblob.vsassets.io
  • 4zjvsblobprodcus390.vsblob.vsassets.io
  • 51yvsblobprodcus36.vsblob.vsassets.io
  • 5dkvsblobprodcus355.vsblob.vsassets.io
  • 5rqvsblobprodcus385.vsblob.vsassets.io
  • 6s7vsblobprodcus313.vsblob.vsassets.io
  • 7devsblobprodcus323.vsblob.vsassets.io
  • 7k6vsblobprodcus337.vsblob.vsassets.io
  • 7tjvsblobprodcus341.vsblob.vsassets.io
  • 80zvsblobprodcus35.vsblob.vsassets.io
  • 8xbvsblobprodcus382.vsblob.vsassets.io
  • 96bvsblobprodcus338.vsblob.vsassets.io
  • ajhvsblobprodcus363.vsblob.vsassets.io
  • bcnvsblobprodcus378.vsblob.vsassets.io
  • builds.dotnet.microsoft.com
  • c50vsblobprodcus330.vsblob.vsassets.io
  • c78vsblobprodcus322.vsblob.vsassets.io
  • cflvsblobprodcus383.vsblob.vsassets.io
  • ci.dot.net
  • ckzvsblobprodcus347.vsblob.vsassets.io
  • d0svsblobprodcus381.vsblob.vsassets.io
  • data.nuget.org
  • dc.services.visualstudio.com
  • dlbvsblobprodcus316.vsblob.vsassets.io
  • e7bvsblobprodcus348.vsblob.vsassets.io
  • fdpvsblobprodcus345.vsblob.vsassets.io
  • frdvsblobprodcus327.vsblob.vsassets.io
  • gbsvsblobprodcus365.vsblob.vsassets.io
  • gervsblobprodcus329.vsblob.vsassets.io
  • h6tvsblobprodcus346.vsblob.vsassets.io
  • i1qvsblobprodcus353.vsblob.vsassets.io
  • ibzvsblobprodcus369.vsblob.vsassets.io
  • imzvsblobprodcus368.vsblob.vsassets.io
  • jd4vsblobprodcus366.vsblob.vsassets.io
  • josvsblobprodcus372.vsblob.vsassets.io
  • jrqvsblobprodcus343.vsblob.vsassets.io
  • k0ivsblobprodcus356.vsblob.vsassets.io
  • k4kvsblobprodcus344.vsblob.vsassets.io
  • kgfvsblobprodcus314.vsblob.vsassets.io
  • kh4vsblobprodcus325.vsblob.vsassets.io
  • kijvsblobprodcus387.vsblob.vsassets.io
  • kmuvsblobprodcus389.vsblob.vsassets.io
  • kxqvsblobprodcus376.vsblob.vsassets.io
  • l49vsblobprodcus358.vsblob.vsassets.io
  • l7avsblobprodcus319.vsblob.vsassets.io
  • ljcvsblobprodcus317.vsblob.vsassets.io
  • lylvsblobprodcus31.vsblob.vsassets.io
  • m16vsblobprodcus374.vsblob.vsassets.io
  • m6xvsblobprodcus342.vsblob.vsassets.io
  • m8dvsblobprodcus37.vsblob.vsassets.io
  • mfjvsblobprodcus373.vsblob.vsassets.io
  • n3kvsblobprodcus335.vsblob.vsassets.io
  • nn8vsblobprodcus340.vsblob.vsassets.io
  • o3svsblobprodcus318.vsblob.vsassets.io
  • ofvvsblobprodcus315.vsblob.vsassets.io
  • p2ovsblobprodcus312.vsblob.vsassets.io
  • pc2vsblobprodcus360.vsblob.vsassets.io
  • pdfvsblobprodcus380.vsblob.vsassets.io
  • pe3vsblobprodcus354.vsblob.vsassets.io
  • pe4vsblobprodcus351.vsblob.vsassets.io
  • pkgs.dev.azure.com
  • rcxvsblobprodcus328.vsblob.vsassets.io
  • s4uvsblobprodcus326.vsblob.vsassets.io
  • s8mvsblobprodcus38.vsblob.vsassets.io
  • sc4vsblobprodcus331.vsblob.vsassets.io
  • se1vsblobprodcus349.vsblob.vsassets.io
  • southcentralus0.in.applicationinsights.azure.com
  • sqdvsblobprodcus333.vsblob.vsassets.io
  • st8vsblobprodcus339.vsblob.vsassets.io
  • tphvsblobprodcus375.vsblob.vsassets.io
  • u3hvsblobprodcus371.vsblob.vsassets.io
  • u6ovsblobprodcus377.vsblob.vsassets.io
  • ukkvsblobprodcus352.vsblob.vsassets.io
  • uy6vsblobprodcus34.vsblob.vsassets.io
  • v53vsblobprodcus320.vsblob.vsassets.io
  • vb4vsblobprodcus33.vsblob.vsassets.io
  • vwvvsblobprodcus334.vsblob.vsassets.io
  • x3yvsblobprodcus370.vsblob.vsassets.io
  • xupvsblobprodcus332.vsblob.vsassets.io
  • yluvsblobprodcus367.vsblob.vsassets.io
  • yttvsblobprodcus357.vsblob.vsassets.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "0t3vsblobprodcus362.vsblob.vsassets.io"
    - "11vvsblobprodcus336.vsblob.vsassets.io"
    - "1javsblobprodcus364.vsblob.vsassets.io"
    - "1k9vsblobprodcus379.vsblob.vsassets.io"
    - "1oavsblobprodcus350.vsblob.vsassets.io"
    - "1p7vsblobprodcus324.vsblob.vsassets.io"
    - "1s1vsblobprodcus386.vsblob.vsassets.io"
    - "2kmvsblobprodcus39.vsblob.vsassets.io"
    - "2zrvsblobprodcus388.vsblob.vsassets.io"
    - "37bvsblobprodcus311.vsblob.vsassets.io"
    - "37cvsblobprodcus359.vsblob.vsassets.io"
    - "4m6vsblobprodcus384.vsblob.vsassets.io"
    - "4myvsblobprodcus32.vsblob.vsassets.io"
    - "4vyvsblobprodcus361.vsblob.vsassets.io"
    - "4zjvsblobprodcus390.vsblob.vsassets.io"
    - "51yvsblobprodcus36.vsblob.vsassets.io"
    - "5dkvsblobprodcus355.vsblob.vsassets.io"
    - "5rqvsblobprodcus385.vsblob.vsassets.io"
    - "6s7vsblobprodcus313.vsblob.vsassets.io"
    - "7devsblobprodcus323.vsblob.vsassets.io"
    - "7k6vsblobprodcus337.vsblob.vsassets.io"
    - "7tjvsblobprodcus341.vsblob.vsassets.io"
    - "80zvsblobprodcus35.vsblob.vsassets.io"
    - "8xbvsblobprodcus382.vsblob.vsassets.io"
    - "96bvsblobprodcus338.vsblob.vsassets.io"
    - "ajhvsblobprodcus363.vsblob.vsassets.io"
    - "bcnvsblobprodcus378.vsblob.vsassets.io"
    - "builds.dotnet.microsoft.com"
    - "c50vsblobprodcus330.vsblob.vsassets.io"
    - "c78vsblobprodcus322.vsblob.vsassets.io"
    - "cflvsblobprodcus383.vsblob.vsassets.io"
    - "ci.dot.net"
    - "ckzvsblobprodcus347.vsblob.vsassets.io"
    - "d0svsblobprodcus381.vsblob.vsassets.io"
    - "data.nuget.org"
    - "dc.services.visualstudio.com"
    - "dlbvsblobprodcus316.vsblob.vsassets.io"
    - "e7bvsblobprodcus348.vsblob.vsassets.io"
    - "fdpvsblobprodcus345.vsblob.vsassets.io"
    - "frdvsblobprodcus327.vsblob.vsassets.io"
    - "gbsvsblobprodcus365.vsblob.vsassets.io"
    - "gervsblobprodcus329.vsblob.vsassets.io"
    - "h6tvsblobprodcus346.vsblob.vsassets.io"
    - "i1qvsblobprodcus353.vsblob.vsassets.io"
    - "ibzvsblobprodcus369.vsblob.vsassets.io"
    - "imzvsblobprodcus368.vsblob.vsassets.io"
    - "jd4vsblobprodcus366.vsblob.vsassets.io"
    - "josvsblobprodcus372.vsblob.vsassets.io"
    - "jrqvsblobprodcus343.vsblob.vsassets.io"
    - "k0ivsblobprodcus356.vsblob.vsassets.io"
    - "k4kvsblobprodcus344.vsblob.vsassets.io"
    - "kgfvsblobprodcus314.vsblob.vsassets.io"
    - "kh4vsblobprodcus325.vsblob.vsassets.io"
    - "kijvsblobprodcus387.vsblob.vsassets.io"
    - "kmuvsblobprodcus389.vsblob.vsassets.io"
    - "kxqvsblobprodcus376.vsblob.vsassets.io"
    - "l49vsblobprodcus358.vsblob.vsassets.io"
    - "l7avsblobprodcus319.vsblob.vsassets.io"
    - "ljcvsblobprodcus317.vsblob.vsassets.io"
    - "lylvsblobprodcus31.vsblob.vsassets.io"
    - "m16vsblobprodcus374.vsblob.vsassets.io"
    - "m6xvsblobprodcus342.vsblob.vsassets.io"
    - "m8dvsblobprodcus37.vsblob.vsassets.io"
    - "mfjvsblobprodcus373.vsblob.vsassets.io"
    - "n3kvsblobprodcus335.vsblob.vsassets.io"
    - "nn8vsblobprodcus340.vsblob.vsassets.io"
    - "o3svsblobprodcus318.vsblob.vsassets.io"
    - "ofvvsblobprodcus315.vsblob.vsassets.io"
    - "p2ovsblobprodcus312.vsblob.vsassets.io"
    - "pc2vsblobprodcus360.vsblob.vsassets.io"
    - "pdfvsblobprodcus380.vsblob.vsassets.io"
    - "pe3vsblobprodcus354.vsblob.vsassets.io"
    - "pe4vsblobprodcus351.vsblob.vsassets.io"
    - "pkgs.dev.azure.com"
    - "rcxvsblobprodcus328.vsblob.vsassets.io"
    - "s4uvsblobprodcus326.vsblob.vsassets.io"
    - "s8mvsblobprodcus38.vsblob.vsassets.io"
    - "sc4vsblobprodcus331.vsblob.vsassets.io"
    - "se1vsblobprodcus349.vsblob.vsassets.io"
    - "southcentralus0.in.applicationinsights.azure.com"
    - "sqdvsblobprodcus333.vsblob.vsassets.io"
    - "st8vsblobprodcus339.vsblob.vsassets.io"
    - "tphvsblobprodcus375.vsblob.vsassets.io"
    - "u3hvsblobprodcus371.vsblob.vsassets.io"
    - "u6ovsblobprodcus377.vsblob.vsassets.io"
    - "ukkvsblobprodcus352.vsblob.vsassets.io"
    - "uy6vsblobprodcus34.vsblob.vsassets.io"
    - "v53vsblobprodcus320.vsblob.vsassets.io"
    - "vb4vsblobprodcus33.vsblob.vsassets.io"
    - "vwvvsblobprodcus334.vsblob.vsassets.io"
    - "x3yvsblobprodcus370.vsblob.vsassets.io"
    - "xupvsblobprodcus332.vsblob.vsassets.io"
    - "yluvsblobprodcus367.vsblob.vsassets.io"
    - "yttvsblobprodcus357.vsblob.vsassets.io"

See Network Configuration for more information.

🤖 Automated content by GitHub Copilot. Generated by the Efficiency Improver workflow. · copilot · auto · 588 AIC · ⌖ 13.9 AIC · ⊞ 21.2K · [◷]( · ◷)

Add this agentic workflow to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/efficiency-improver.md@main
Dominant language
C#
Stars
1k
Forks
312
Avg merge
8h 14m
Merged PRs (30d)
497

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/testfx

All issues in microsoft/testfx

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.