You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
π€ Daily Efficiency Improver β automated AI assistant reducing the energy footprint of this repository.
Goal and Rationale
Eliminate two unnecessary heap allocations that occur on every test execution in the test runner hot path:
TestMethodInfo.GetRetryAttribute(): Called from the TestMethodInfo constructor (once per test). Was using GetAttributes<RetryBaseAttribute>(), a yield return iterator that allocates a compiler-generated state machine object even when no retry attribute is present (the common case).
TestMethodRunner.TryExecuteDataSourceBasedTestsAsync(): Was allocating a DataSourceAttribute[] array (via GetAttributes<DataSourceAttribute>()) on every test execution before checking whether data-source tests exist β for the ~99% of tests that don't use [DataSource].
Focus Area
Code-Level Efficiency β eliminating per-test-execution heap allocations on the hot path.
Approach
Fix 1 β TestMethodInfo.GetRetryAttribute()
Before:GetAttributes<RetryBaseAttribute>() (allocates yield iterator state machine + enumerator)
After: Direct iteration over the cached attribute array (zero allocations)
// PERF: Iterate the cached attribute array directly instead of allocating// a yield iterator state machine via GetAttributes<RetryBaseAttribute>().Attribute[]cachedAttributes=ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo);RetryBaseAttribute?found=null;foreach(AttributeattributeincachedAttributes){if(attributeisRetryBaseAttributeretry){if(foundis not null)ThrowMultipleAttributesException(nameof(RetryBaseAttribute));found=retry;}}returnfound;
After:IsAttributeDefined<DataSourceAttribute>() walks cached array with zero allocation:
// PERF: Use IsAttributeDefined instead of GetAttributes<T>() to avoid allocating// a yield iterator state machine + array on every test execution for the common case.if(!ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(_testMethodInfo.MethodInfo)){returnfalse;}
Energy Efficiency Evidence
Proxy metric: heap allocation count per test execution
(fewer allocations β less GC pressure β less CPU in collection β less energy)
Site
Before
After
GetRetryAttribute per test
1 state machine (~48 B)
0
TryExecuteDataSourceBasedTestsAsync per non-DataSource test
array + state machine (~96 B)
0
Combined savings at 10,000 tests
~1.4 MB GC roots eliminated
β
The improvements apply to every test in the common case (no [Retry] / [DataSource] attribute).
Measurement approach: Verifiable with BenchmarkDotNet [MemoryDiagnoser] targeting TestMethodInfo construction and TryExecuteDataSourceBasedTestsAsync invocations.
Green Software Foundation context:Hardware Efficiency β less memory churn means DRAM and CPU do more useful work per joule. Energy Proportionality β GC cost scales linearly with allocation rate.
Trade-offs
GetRetryAttribute is slightly more verbose, but follows the same pattern as GetFirstAttributeOrDefault() / GetSingleAttributeOrDefault() already in ReflectHelper.
Semantics are fully preserved: multi-attribute detection still throws; IsAttributeDefined uses the same cached array as GetAttributes.
The patch file is available in the agent artifact in the workflow run linked above.
To create a pull request with the changes:
# Download the artifact from the workflow run
gh run download 25619883483 -n agent -D /tmp/agent-25619883483
# Create a new branch
git checkout -b efficiency/avoid-yield-iterator-allocs-v4-9a3ee2364a017ed4
# Apply the patch (--3way handles cross-repo patches where files may already exist)
git am --3way /tmp/agent-25619883483/aw-efficiency-avoid-yield-iterator-allocs-v4.patch
# Push the branch to origin
git push origin efficiency/avoid-yield-iterator-allocs-v4-9a3ee2364a017ed4
# Create the pull request
gh pr create --title '[Efficiency Improver] perf: avoid yield iterator allocations in test execution hot path' --base main --head efficiency/avoid-yield-iterator-allocs-v4-9a3ee2364a017ed4 --repo microsoft/testfx
Show patch preview (100 of 100 lines)
From 6eb3aa80a8d1a4bdfcf3da193c38ffcd946d9bb2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 28 Apr 2026 13:44:11 +0000
Subject: [PATCH] perf: avoid yield iterator allocations in test execution hot
path
In the test execution hot path, eliminate LINQ/iterator allocations
that happen for every test method invocation:
1. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync: replace
_testMethodInfo.GetAttributes<DataSourceAttribute>() (which allocates
a yield iterator state machine + DataSourceAttribute[] array) with
ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>() which
directly iterates the cached Attribute[] without any extra allocation.
Since DataSourceAttribute has AllowMultiple=false, a Length:1 check is
equivalent to an existence check. Saves 2 allocations per test execution
for the common case (non-DataSource tests).
2. TestMethodInfo.GetRetryAttribute: replace GetAttributes<RetryBaseAttribute>
(yield iterator) + IEnumerator<T> (enumerator) with direct iteration of
GetCustomAttributesCached(). Eliminates the iterator state machine
allocation per test execution. Preserves the duplicate-attribute check
and custom exception.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Execution/TestMethodInfo.cs | 25 +++++++++++--------
.../Execution/TestMethodRunner.cs | 11 ++++----
2 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
index e45ed74e5..c3853520c 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs@@ -289,21 +289,24 @@ private TestMethodAttribute GetTestMethodAttribute()
/// </returns>
private RetryBaseAttribu
... (truncated)
π€ Daily Efficiency Improver β automated AI assistant reducing the energy footprint of this repository.
Goal and Rationale
Eliminate two unnecessary heap allocations that occur on every test execution in the test runner hot path:
TestMethodInfo.GetRetryAttribute(): Called from theTestMethodInfoconstructor (once per test). Was usingGetAttributes<RetryBaseAttribute>(), ayield returniterator that allocates a compiler-generated state machine object even when no retry attribute is present (the common case).TestMethodRunner.TryExecuteDataSourceBasedTestsAsync(): Was allocating aDataSourceAttribute[]array (viaGetAttributes<DataSourceAttribute>()) on every test execution before checking whether data-source tests exist β for the ~99% of tests that don't use[DataSource].Focus Area
Code-Level Efficiency β eliminating per-test-execution heap allocations on the hot path.
Approach
Fix 1 β
TestMethodInfo.GetRetryAttribute()Before:
GetAttributes<RetryBaseAttribute>()(allocates yield iterator state machine + enumerator)After: Direct iteration over the cached attribute array (zero allocations)
Fix 2 β
TestMethodRunner.TryExecuteDataSourceBasedTestsAsync()Before: Allocates array + state machine, then checks length:
After:
IsAttributeDefined<DataSourceAttribute>()walks cached array with zero allocation:Energy Efficiency Evidence
Proxy metric: heap allocation count per test execution
(fewer allocations β less GC pressure β less CPU in collection β less energy)
GetRetryAttributeper testTryExecuteDataSourceBasedTestsAsyncper non-DataSource testThe improvements apply to every test in the common case (no
[Retry]/[DataSource]attribute).Measurement approach: Verifiable with BenchmarkDotNet
[MemoryDiagnoser]targetingTestMethodInfoconstruction andTryExecuteDataSourceBasedTestsAsyncinvocations.Green Software Foundation context: Hardware Efficiency β less memory churn means DRAM and CPU do more useful work per joule. Energy Proportionality β GC cost scales linearly with allocation rate.
Trade-offs
GetRetryAttributeis slightly more verbose, but follows the same pattern asGetFirstAttributeOrDefault()/GetSingleAttributeOrDefault()already inReflectHelper.IsAttributeDefineduses the same cached array asGetAttributes.Reproducibility
Test Status
Note
This was originally intended as a pull request, but the git push operation failed.
Workflow Run: View run details and download patch artifact
The patch file is available in the
agentartifact in the workflow run linked above.To create a pull request with the changes:
Show patch preview (100 of 100 lines)