Skip to content

[efficiency-improver] perf: single-pass PropertyBag walk in JUnitReport TestResultCapture#9018

Draft
Evangelink wants to merge 1 commit into
mainfrom
efficiency/junit-single-pass-property-walk-57947c21bec7db5a
Draft

[efficiency-improver] perf: single-pass PropertyBag walk in JUnitReport TestResultCapture#9018
Evangelink wants to merge 1 commit into
mainfrom
efficiency/junit-single-pass-property-walk-57947c21bec7db5a

Conversation

@Evangelink

Copy link
Copy Markdown
Member

🤖 This is a draft PR created by Efficiency Improver, an automated AI assistant focused on reducing the energy consumption and computational footprint of this repository.

Goal and Rationale

Reduce redundant CPU work and heap allocations in TestResultCapture.TryCapture() in Microsoft.Testing.Extensions.JUnitReport, which runs on every completed test result when JUnit report generation (--report-junit) is enabled.

Focus area: Code-Level Efficiency — eliminating unnecessary linked-list traversals and heap allocations per test result.

Approach

TestResultCapture.TryCapture() previously made 6 separate passes over the PropertyBag linked list:

  • 5 × SingleOrDefault<T>() (for TestNodeStateProperty, TimingProperty, TestMethodIdentifierProperty, StandardOutputProperty, StandardErrorProperty)
  • 1 × foreach (IProperty p in node.Properties) — calls the public GetEnumerator() which boxes the internal struct enumerator as IEnumerator<IProperty> (1 heap allocation)

The fix replaces all of these with a single GetStructEnumerator() pass using a switch on the current node, collecting all required properties in one traversal. GetClassAndMethodName() is updated to accept the already-collected TestMethodIdentifierProperty directly, removing the need for an internal walk.

Energy Efficiency Evidence

Proxy metric used: CPU cycles / heap allocations per test result (maps directly to energy — fewer pointer dereferences and GC pressure = less power draw per test run).

Metric Before After
Linked-list walks per terminal test result 6 1
IEnumerator<IProperty> heap allocations per test result 1 (from foreach) 0
Linked-list nodes visited (N=10 props) ~60 ~10

For a test run with 1 000 tests: ~50 000 pointer dereferences eliminated and ~1 000 heap allocations removed.

Reproducibility: Build src/Platform/Microsoft.Testing.Extensions.JUnitReport — succeeded with 0 warnings, 0 errors.

Green Software Foundation Context

Hardware Efficiency: Fewer repeated traversals of the same linked-list nodes reduces CPU cache pressure. Fewer cache misses → lower power draw per test result processed.

Software Carbon Intensity (SCI): Fewer instructions per functional unit (one test result processed) directly reduces the energy term in the SCI equation.

Trade-offs

The single-pass switch block is slightly more verbose than the previous sequence of named SingleOrDefault<T>() calls. However, the pattern is now established in the codebase (see DotnetTestDataConsumer, OpenTelemetryResultHandler, TrxTestResultExtractor) and is recognisable to contributors familiar with those files.

Test Status

✅ Build succeeded (0 warnings, 0 errors) for Microsoft.Testing.Extensions.JUnitReport

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Efficiency Improver workflow.

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Efficiency Improver workflow.{ai_credits_suffix} · [◷]( · )

Add this agentic workflows to your repo

To install this agentic workflow, run

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

Replace 5 × SingleOrDefault<T>() + 1 × foreach/GetEnumerator() in
TestResultCapture.TryCapture() with a single GetStructEnumerator() pass,
saving 5 linked-list traversals and 1 IEnumerator<IProperty> heap allocation
per terminal test result when --report-junit is enabled.

Also update GetClassAndMethodName() to accept the already-collected
TestMethodIdentifierProperty directly, removing the final redundant walk.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 10, 2026 22:39
@Evangelink Evangelink added area/performance Runtime / build performance / efficiency. type/automation Created or maintained by an agentic workflow. labels Jun 10, 2026

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 Microsoft.Testing.Extensions.JUnitReport’s TestResultCapture.TryCapture() by consolidating multiple PropertyBag lookups into a single struct-enumerator traversal, reducing linked-list walks and avoiding IEnumerator<IProperty> boxing allocations on terminal test results.

Changes:

  • Replaced multiple SingleOrDefault<T>() calls and foreach enumeration with a single GetStructEnumerator() pass collecting all needed properties.
  • Refactored GetClassAndMethodName() to accept the already-collected TestMethodIdentifierProperty, avoiding an additional property bag lookup.
  • Simplified stdout/stderr and trait collection to reuse the same traversal.
Show a summary per file
File Description
src/Platform/Microsoft.Testing.Extensions.JUnitReport/TestResultCapture.cs Consolidates property extraction into a single struct-enumerator pass and updates method-name extraction to reuse collected data.

Copilot's findings

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

Comment on lines +42 to +46
// Single-pass collection of all required properties — replaces 5 × SingleOrDefault<T>()
// + 1 × foreach/GetEnumerator() with one zero-allocation GetStructEnumerator() pass,
// saving 5 linked-list traversals and 1 IEnumerator<IProperty> heap allocation per
// terminal test result. Singleton-typed properties use the local GetSingleOrDefaultValue
// helper to preserve the throw-on-duplicate invariant that SingleOrDefault<T>() provided;
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.

2 participants