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 Perf Improver, an automated AI assistant focused on performance improvements.
Goal and Rationale
Reduce heap allocations in AttributeExtensions.IsIgnored, called twice per test execution — once for the test class and once for the test method. Allocations here scale linearly with test count.
A yield iterator state machine from GetAttributes<T>() (even when no ConditionBaseAttribute is present)
A LINQ GroupBy operator object
After:
Direct iteration of GetCustomAttributesCached()'s Attribute[] result. Grouping state is tracked via two lazily-allocated dictionaries (unsatisfiedGroups, satisfiedGroups) that are only created when a ConditionBaseAttribute is found — the uncommon case for most tests.
This follows the established allocation-free pattern used by GetTestPropertiesAsTraits and GetTestCategories in the same file.
Performance Evidence
Scenario
Before (objects)
After
No ConditionBaseAttribute (common)
1 yield iterator + 1 GroupBy
0
[Ignore] present on class/method
1 yield iterator + LINQ Lookup internals
1 Dictionary + 1 HashSet
Per test execution (class + method)
~4 iterator/LINQ objects
0 (common case)
For 1,000 tests: eliminates ~4,000 short-lived LINQ objects per run.
Trade-offs
Manual grouping is slightly more verbose, but semantics are identical: OR within group, AND across groups, first non-null ignore message reported.
The dictionaries allocated in the uncommon case ([Ignore] present) replace LINQ's Lookup<> internals — comparable allocation profile.
✅ MSTestAdapter.PlatformServices builds with 0 warnings, 0 errors on net8.0.
Note: MSTestAdapter.PlatformServices.UnitTests has a pre-existing build failure in this environment due to AwesomeAssertions 9.3.0 only shipping netstandard2.1 (no net8.0 folder). This is unrelated to these changes. CI will run the full test suite.
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 25602028457 -n agent -D /tmp/agent-25602028457
# Create a new branch
git checkout -b perf-assist/isignored-linq-elimination-v9-3a48b53cb26fe8b1
# Apply the patch (--3way handles cross-repo patches where files may already exist)
git am --3way /tmp/agent-25602028457/aw-perf-assist-isignored-linq-elimination-v9.patch
# Push the branch to origin
git push origin perf-assist/isignored-linq-elimination-v9-3a48b53cb26fe8b1
# Create the pull request
gh pr create --title '[Perf Improver] perf: eliminate LINQ allocations in IsIgnored hot path' --base main --head perf-assist/isignored-linq-elimination-v9-3a48b53cb26fe8b1 --repo microsoft/testfx
Show patch preview (97 of 97 lines)
From 45e06d1ffaa68c204fb115a71fab908967bdeab5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Sat, 9 May 2026 13:25:00 +0000
Subject: [PATCH] perf: eliminate LINQ allocations in IsIgnored hot path
Replace GetAttributes<T>() yield iterator + LINQ GroupBy with direct
iteration of GetCustomAttributesCached()'s Attribute[] result. Grouping
is tracked via lazily-allocated dictionaries (only when a
ConditionBaseAttribute is present, which is the uncommon case for most
tests).
Common case (no [Ignore] / no OSCondition): 0 allocations vs 2 before.
Per-test savings (class + method call): ~4 short-lived objects eliminated.
For 1,000 tests: ~4,000 LINQ objects eliminated per run.
Closes #7992
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Helpers/AttributeHelpers.cs | 55 +++++++++++++------
1 file changed, 39 insertions(+), 16 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs
index a2bbcbe..56ba75b 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs+++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs@@ -9,27 +9,50 @@ internal static class AttributeExtensions
{
public static bool IsIgnored(this ICustomAttributeProvider type, out string? ignoreMessage)
{
- IEnumerable<ConditionBaseAttribute> attributes = ReflectHelper.Instance.GetAttributes<ConditionBaseAttribute>(type);- IEnumerable<IGrouping<string, ConditionBaseAttribute>> groups = attributes.GroupBy(attr => attr.GroupName);- foreach (IGrouping<string, ConditionBaseAttribute>? group in groups)+ // Use the cached Attribute[] directly to avoid the yield iterator allocation from GetAttributes<T>()+ // and the LINQ GroupBy operator allocation. Grouping is tracked lazily via dictionaries that are+
... (truncated)
🤖 This PR was created by Perf Improver, an automated AI assistant focused on performance improvements.
Goal and Rationale
Reduce heap allocations in
AttributeExtensions.IsIgnored, called twice per test execution — once for the test class and once for the test method. Allocations here scale linearly with test count.Approach
Before:
Every call allocates:
GetAttributes<T>()(even when noConditionBaseAttributeis present)GroupByoperator objectAfter:
Direct iteration of
GetCustomAttributesCached()'sAttribute[]result. Grouping state is tracked via two lazily-allocated dictionaries (unsatisfiedGroups,satisfiedGroups) that are only created when aConditionBaseAttributeis found — the uncommon case for most tests.This follows the established allocation-free pattern used by
GetTestPropertiesAsTraitsandGetTestCategoriesin the same file.Performance Evidence
ConditionBaseAttribute(common)[Ignore]present on class/methodFor 1,000 tests: eliminates ~4,000 short-lived LINQ objects per run.
Trade-offs
[Ignore]present) replace LINQ'sLookup<>internals — comparable allocation profile.Reproducibility
Test Status
✅
MSTestAdapter.PlatformServicesbuilds with 0 warnings, 0 errors on net8.0.Closes #7992
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 (97 of 97 lines)