Skip to content

[efficiency-improver] perf: single-pass TestNode property serialization in SerializerUtilities#8806

Merged
Evangelink merged 2 commits into
mainfrom
efficiency/single-pass-testnode-serializer-468d952a5dfc8bd1
Jun 4, 2026
Merged

[efficiency-improver] perf: single-pass TestNode property serialization in SerializerUtilities#8806
Evangelink merged 2 commits into
mainfrom
efficiency/single-pass-testnode-serializer-468d952a5dfc8bd1

Conversation

@Evangelink
Copy link
Copy Markdown
Member

🤖 Efficiency Improver — automated AI assistant focused on reducing the energy consumption and computational footprint of this repository.

Goal and Rationale

Eliminate a redundant enumeration of TestNode.Properties during JSON-RPC serialization. This is the server-mode hot path — called for every TestNode pushed from the test host to IDEs (VS, VS Code, Rider) over the JSON-RPC channel.

Focus Area

Code-Level Efficiency — unnecessary object allocation and CPU work per test result.

Approach

Previously, Properties was traversed twice per TestNode:

  1. n.Properties.OfType<TestMetadataProperty>() — allocates an intermediate array to collect trait metadata
  2. A foreach loop over all properties to handle all other types

Now a single foreach handles all property types. TestMetadataProperty items are collected into a lazy-allocated List<KVP> (NETCOREAPP) or JsonArray (netfx), assigned to "traits" after the loop.

Energy Efficiency Evidence

Proxy metric: heap allocation count and CPU instructions.

  • Before: 1 extra full Properties scan + 1 intermediate TestMetadataProperty[] allocation per TestNode serialized
  • After: single scan, no intermediate array unless traits are present (lazy ??=)

For a typical IDE session with 1 000 tests, this eliminates ≥1 000 unnecessary array allocations and 1 000 extra PropertyBag traversals during serialization, reducing GC pressure and CPU cycles in the JSON-RPC hot path.

GSF — Hardware Efficiency: fewer allocations → less GC work → better utilization of the CPU and DRAM already in use.

Trade-offs

  • Traits are now assigned after the loop rather than before — the JSON key order in "traits" may differ from the previous order (traits appear after other properties rather than near the top). JSON-RPC consumers must not depend on key ordering (the spec does not guarantee it).
  • Slightly more code lines due to #if NETCOREAPP branching, but the logic is straightforward.

Reproducibility

./build.sh   # verify build success

Test Status

✅ Build succeeded (0 errors, 0 warnings)

Generated by Efficiency Improver · sonnet46 1.9M ·

Add this agentic workflows to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/efficiency-improver.md@main

Eliminate a redundant enumeration of TestNode.Properties during JSON-RPC
serialization. Previously, Properties was traversed twice:
1. OfType<TestMetadataProperty>() to collect trait metadata
2. A foreach loop to process all other properties

Now a single foreach handles all property types, collecting
TestMetadataProperty items into a lazy-allocated list/JsonArray that is
assigned to 'traits' after the loop. This removes one full Properties
scan per TestNode sent over the server-mode JSON-RPC channel.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 3, 2026 22:42
@Evangelink Evangelink added area/performance Runtime / build performance / efficiency. type/automation Created or maintained by an agentic workflow. labels Jun 3, 2026
@Evangelink Evangelink marked this pull request as ready for review June 3, 2026 22:47
Copy link
Copy Markdown
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 PR targets the JSON-RPC server-mode serialization path for TestNode by reducing work performed while serializing TestNode.Properties in SerializerUtilities, aiming to lower allocations and CPU usage.

Changes:

  • Refactors TestNode serialization in SerializerUtilities to collect TestMetadataProperty (“traits”) during the main foreach over n.Properties (single pass).
  • Lazily allocates the traits container (List<KeyValuePair<string,string>> on NETCOREAPP, JsonArray on non-NETCOREAPP) only when traits are present.
Show a summary per file
File Description
src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs Converts TestNode.Properties trait handling to a single-pass collection with lazy allocation.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 2

Comment thread src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs Outdated
- SerializerUtilities: Reserve 	raits slot up-front so the serialized JSON keeps the original wire format (traits right after display-name). Fixes FormatterUtilitiesTests on the !NETCOREAPP path (Jsonite).

- SerializerUtilities: Unify the node-type fallback under the !ContainsKey guard for both NETCOREAPP and !NETCOREAPP, avoiding a latent ArgumentException when a TestNodeStateProperty was present.

- Json.cs: Apply the same single-pass TestMetadataProperty collection used in SerializerUtilities; Insert the traits entry at index 2 after the loop to preserve key ordering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink
Copy link
Copy Markdown
Member Author

Addressed the review feedback and fixed the failing build:

Build failure (net462 only) in FormatterUtilitiesTests: The PR was moving traits to the end of the serialized JSON, breaking the wire-format-ordering assertions. Net8.0/net9.0 weren't affected because the NETCOREAPP path uses Json.cs (not modified). Fixed in SerializerUtilities.cs by reserving the traits slot up-front (placeholder null immediately after display-name) and either assigning the collected traits or removing the placeholder after the single-pass loop. Wire format is now preserved.

Review comment 1 (NETCOREAPP properties.Add(\"node-type\", \"group\")): Unified both NETCOREAPP and !NETCOREAPP paths under the !properties.ContainsKey(\"node-type\") guard so the latent ArgumentException can no longer happen.

Review comment 2 (apply same change to Json.cs): Done. The single-pass collects TestMetadataProperty lazily into a List<KeyValuePair<string, string>>?, then properties.Insert(2, (\"traits\", traits)) at the end preserves the existing ordering (uid, display-name, traits, ...).

All 1059 tests pass on net9.0 and all 1050 tests pass on net462 locally.

@Evangelink Evangelink enabled auto-merge (squash) June 4, 2026 16:44
@Evangelink Evangelink merged commit bb5be03 into main Jun 4, 2026
41 checks passed
@Evangelink Evangelink deleted the efficiency/single-pass-testnode-serializer-468d952a5dfc8bd1 branch June 4, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/performance Runtime / build performance / efficiency. type/automation Created or maintained by an agentic workflow.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants