Skip to content

[perf-improver] perf: skip intermediate Dictionary alloc in CloneForDataDrivenIteration #9634

Description

@github-actions

Goal and Rationale

CloneForDataDrivenIteration in TestContextImplementation previously created an intermediate Dictionary<string, object?> copy before passing it to the constructor:

varsnapshot=newDictionary<string,object?>(_properties);varclone=newTestContextImplementation(testMethod:null,testClassFullName:null,snapshot, ...);

The constructor's null/null branch (testMethod == null && testClassFullName == null) immediately copies snapshot into another new Dictionary via the [with(properties)] collection-expression spread:

_properties=[with(properties)];// creates yet another Dictionary

This meant 2 Dictionary allocations + 2× O(n) copies per data-driven test iteration, but the snapshot intermediate was redundant — the constructor copy alone is sufficient for isolation.

Approach

Pass _properties directly to the constructor:

TestContextImplementationclone=new(testMethod:null,testClassFullName:null,_properties,_messageLogger,_testRunCancellationToken);

The constructor still copies _properties into its own fresh Dictionary (via [with(properties)]), so:

  • Clone mutations do not leak back to the original
  • Original mutations after clone creation do not leak to the clone
  • All existing isolation invariants are preserved

Performance Evidence

Saved allocations per data-driven test iteration (common case):

  • Before: 1× intermediate Dictionary<string, object?> + 1× constructor Dictionary = 2 allocs + 2× O(n) copy
  • After: 1× constructor Dictionary = 1 alloc + 1× O(n) copy

For a suite with 100 data-driven tests × 10 rows each = 1000 iterations → 1000 fewer Dictionary allocations (typically 200–500 bytes each on the heap).

Methodology: direct inspection of the allocation path; the intermediate snapshot variable is not used anywhere other than the constructor call it was immediately passed to.

Trade-offs

None. This is a straightforward removal of a redundant copy. Correctness is guaranteed by the constructor's own copy, which is unchanged.

Reproducibility

# Inspect the before/after with:
dotnet run --project test/UnitTests/MSTestAdapter.PlatformServices.UnitTests \
-f net9.0 --no-build -- --treenode-filter "*/*/TestContextImplementationTests/*"

Test Status

Unit tests for CloneForDataDrivenIteration in TestContextImplementationTests.cs cover isolation, shallow copy, and property-bag independence — all pass unchanged. CI will verify the full build.

🤖 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 Perf Improver workflow. · 234.5 AIC · ⌖ 17.1 AIC · ⊞ 12.5K · [◷]( · )

Add this agentic workflows to your repo

To install this agentic workflow, run

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

Note

This was originally intended as a pull request, but GitHub Actions is not permitted to create or approve pull requests in this repository.
The changes have been pushed to branch perf-assist/skip-clone-dict-alloc-3b05866db3010d8c.

Click here to create the pull request

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

Show patch preview (51 of 51 lines)
From 6203d8903a79596a057b7f9750f46da0374f94b9 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Sun, 5 Jul 2026 14:11:29 +0000
Subject: [PATCH] perf: skip intermediate Dictionary alloc in
CloneForDataDrivenIteration
The constructor's null/null branch already copies the supplied properties
dictionary into a fresh Dictionary<string, object?> via the
[with(properties)] collection-expression spread. Passing _properties
directly to the constructor instead of wrapping it in a
"new Dictionary<string, object?>(_properties)" first avoids one
heap allocation and one O(n) copy per data-driven test iteration.
Isolation is preserved: the constructor copy means mutations on the
clone do not leak back to the original context, and mutations on the
original after clone creation do not leak to the clone.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Services/TestContextImplementation.cs | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs
index 5efc98a..e0805e5 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs+++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs@@ -524,16 +524,12 @@ private SynchronizedStringBuilder GetTestContextMessagesStringBuilder()
/// <returns>A fresh context suitable for one folded data-driven iteration.</returns>
internal TestContextImplementation CloneForDataDrivenIteration()
{
- // Take a shallow snapshot of the current property bag so that the clone starts with- // the same properties (including TestNameLabel / FullyQualifiedTestClassNameLabel and- // anything merged from AssemblyInitialize / ClassInitialize) but is otherwise isolated.+ // Pas
... (truncated)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions