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
🤖 This PR was created by Daily Efficiency Improver, an automated AI assistant focused on reducing the energy consumption and computational footprint of this repository.
TestMethodInfo.GetRetryAttribute() was called via GetAttributes<RetryBaseAttribute>(), a yield return iterator method. Every call to a yield return method allocates a compiler-generated state machine object on the heap, even when the attribute is absent (the common case). Since GetRetryAttribute() is called from the TestMethodInfo constructor, and TestMethodInfo is created fresh for every test execution, this allocation occurs once per test.
Focus Area
Code-Level Efficiency — eliminating unnecessary heap allocations on the per-test execution hot path.
Approach
Replace GetAttributes<RetryBaseAttribute>(MethodInfo) with direct iteration over GetCustomAttributesCached(MethodInfo), which returns the already-cached Attribute[] for the method. This is the same pattern used by GetFirstAttributeOrDefault() and GetSingleAttributeOrDefault() in ReflectHelper, which were already optimised for the same reason.
RetryBaseAttribute?found=null;foreach(AttributeattributeinReflectHelper.Instance.GetCustomAttributesCached(MethodInfo)){if(attributeisRetryBaseAttributeretryAttribute){if(foundis not null){ThrowMultipleAttributesException(nameof(RetryBaseAttribute));}found=retryAttribute;}}returnfound;
Energy Efficiency Evidence
Proxy metric: heap allocation count per test (fewer allocations = less GC pressure = less CPU time spent in collection = less energy consumed per test run)
Measurement
Before
After
Iterator state machine allocations per GetRetryAttribute() call
1 (compiler-generated state machine for GetAttributes<T>)
0
Approximate bytes per allocation
~48 bytes
0
Savings at 10,000 tests
~480 KB eliminated from GC heap
—
The reduction is deterministic and applies to every test run regardless of whether RetryAttribute is present (the common case where it is absent still allocated the state machine before this fix).
Measurement approach: The before/after can be verified with a memory profiler (dotMemory, BenchmarkDotNet [MemoryDiagnoser]) targeting TestMethodInfo constructor invocations. The proxy metric (allocation count) maps to energy reduction via reduced GC collection frequency and shorter GC pause times.
🌱 Green Software Foundation context:Hardware Efficiency principle — reducing unnecessary memory churn enables the CPU and DRAM to handle more useful work per joule. Energy Proportionality — GC CPU cost scales with allocation rate; eliminating one allocation per test reduces the cumulative GC overhead proportionally with suite size.
Trade-offs
Complexity: Slightly more verbose than the one-liner GetAttributes<T>() call, but follows the same pattern already established in ReflectHelper.GetFirstAttributeOrDefault() and GetSingleAttributeOrDefault().
Readability: A brief comment explains the motivation.
Correctness: Behaviour is identical — finds all RetryBaseAttribute-derived instances, throws on multiples, returns null when absent.
Reproducibility
# Run the MSTest unit tests covering RetryAttribute behaviorexport PATH="$PWD/.dotnet:$PATH"
dotnet test test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ \
-p:TargetFramework=net8.0 --no-restore \
--filter "RetryAttribute" -p:SignAssembly=false
Test Status
⚠️ Local build infrastructure issue: the agent environment has only the .NET 11 preview SDK installed, which lacks net8.0/net9.0 targeting packs. CI is authoritative for build and test results. The change is a straightforward mechanical substitution with no logic changes.
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 25536495961 -n agent -D /tmp/agent-25536495961
# Create a new branch
git checkout -b efficiency/avoid-iterator-alloc-getretryattribute-v2-33f791c696a6c4b5
# Apply the patch (--3way handles cross-repo patches where files may already exist)
git am --3way /tmp/agent-25536495961/aw-efficiency-avoid-iterator-alloc-getretryattribute-v2.patch
# Push the branch to origin
git push origin efficiency/avoid-iterator-alloc-getretryattribute-v2-33f791c696a6c4b5
# Create the pull request
gh pr create --title '[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()' --base main --head efficiency/avoid-iterator-alloc-getretryattribute-v2-33f791c696a6c4b5 --repo microsoft/testfx
Show patch preview (71 of 71 lines)
From 915dea01f53d5f793da7954a03a12137b1e422d3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Fri, 8 May 2026 04:26:35 +0000
Subject: [PATCH] perf: avoid iterator allocation in GetRetryAttribute()
Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.
Closes #8040
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Execution/TestMethodInfo.cs | 25 +++++++++++--------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
index e45ed74e5..4a2f622b6 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 RetryBaseAttribute? GetRetryAttribute()
{
- IEnumerable<RetryBaseAttribute> attributes = ReflectHelper.Instance.GetAttributes<RetryBaseAttribute>(MethodInfo);- using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator();- if (!enumerator.MoveNext())+ // Iterate the cached attribute
... (truncated)
🤖 This PR was created by Daily Efficiency Improver, an automated AI assistant focused on reducing the energy consumption and computational footprint of this repository.
Closes #8040
Goal and Rationale
TestMethodInfo.GetRetryAttribute()was called viaGetAttributes<RetryBaseAttribute>(), ayield returniterator method. Every call to ayield returnmethod allocates a compiler-generated state machine object on the heap, even when the attribute is absent (the common case). SinceGetRetryAttribute()is called from theTestMethodInfoconstructor, andTestMethodInfois created fresh for every test execution, this allocation occurs once per test.Focus Area
Code-Level Efficiency — eliminating unnecessary heap allocations on the per-test execution hot path.
Approach
Replace
GetAttributes<RetryBaseAttribute>(MethodInfo)with direct iteration overGetCustomAttributesCached(MethodInfo), which returns the already-cachedAttribute[]for the method. This is the same pattern used byGetFirstAttributeOrDefault()andGetSingleAttributeOrDefault()inReflectHelper, which were already optimised for the same reason.Before:
After:
Energy Efficiency Evidence
Proxy metric: heap allocation count per test (fewer allocations = less GC pressure = less CPU time spent in collection = less energy consumed per test run)
GetRetryAttribute()callGetAttributes<T>)The reduction is deterministic and applies to every test run regardless of whether
RetryAttributeis present (the common case where it is absent still allocated the state machine before this fix).Measurement approach: The before/after can be verified with a memory profiler (dotMemory, BenchmarkDotNet
[MemoryDiagnoser]) targetingTestMethodInfoconstructor invocations. The proxy metric (allocation count) maps to energy reduction via reduced GC collection frequency and shorter GC pause times.🌱 Green Software Foundation context: Hardware Efficiency principle — reducing unnecessary memory churn enables the CPU and DRAM to handle more useful work per joule. Energy Proportionality — GC CPU cost scales with allocation rate; eliminating one allocation per test reduces the cumulative GC overhead proportionally with suite size.
Trade-offs
GetAttributes<T>()call, but follows the same pattern already established inReflectHelper.GetFirstAttributeOrDefault()andGetSingleAttributeOrDefault().RetryBaseAttribute-derived instances, throws on multiples, returnsnullwhen absent.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 (71 of 71 lines)