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 AttributeHelpers.IsIgnored, which is called twice per test execution β once for the test class (ClassType.IsIgnored) and once for the test method (MethodInfo.IsIgnored). Allocations here scale linearly with test count.
A yield iterator state machine from GetAttributes (even when no ConditionBaseAttribute is present)
A LINQ GroupBy operator object (closure + Lookup internals)
After:
Attribute[]attributes=ReflectHelper.Instance.GetCustomAttributesCached(type);// Fast path: scan for any ConditionBaseAttribute β zero allocations if none found// Slow path: manual Dictionary-based grouping (one Dictionary, no LINQ)
Direct iteration of the cached Attribute[] β no iterator, no LINQ operator. The Dictionary in the slow path is allocated only when a ConditionBaseAttribute is present (the uncommon case).
Performance Evidence
Methodology: Allocation count per IsIgnored call.
Scenario
Before (objects)
After
No ConditionBaseAttribute (common)
1 yield iterator + 1 GroupBy operator
0
[Ignore] present on class/method
1 yield iterator + LINQ Lookup internals
1 Dictionary
Per test execution (class + method)
~4 objects
0 (common case)
For a test suite with 1,000 tests: eliminates ~2,000β4,000 short-lived LINQ objects per run in the typical case.
Trade-offs
Manual grouping is slightly more verbose, but follows the established allocation-free pattern used by GetTestPropertiesAsTraits and GetTestCategories in the same codebase.
The Dictionary allocated in the uncommon [Ignore] case replaces a LINQ Lookup<> β comparable allocation profile.
Semantics are identical: OR within group, AND across groups, first non-satisfied message reported.
β MSTestAdapter.PlatformServices builds with 0 warnings, 0 errors on net8.0.
Note: MSTestAdapter.PlatformServices.UnitTests has a pre-existing build failure in this environment (AwesomeAssertions 9.3.0 only ships netstandard2.1, not net8.0). 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 25437832278 -n agent -D /tmp/agent-25437832278
# Create a new branch
git checkout -b perf-assist/eliminate-linq-allocations-isignored-may6-7ff99f193b9ef432
# Apply the patch (--3way handles cross-repo patches where files may already exist)
git am --3way /tmp/agent-25437832278/aw-perf-assist-eliminate-linq-allocations-isignored-may6.patch
# Push the branch to origin
git push origin perf-assist/eliminate-linq-allocations-isignored-may6-7ff99f193b9ef432
# Create the pull request
gh pr create --title '[Perf Improver] perf: eliminate LINQ allocations in IsIgnored hot path' --base main --head perf-assist/eliminate-linq-allocations-isignored-may6-7ff99f193b9ef432 --repo microsoft/testfx
Show patch preview (100 of 100 lines)
From 3c99f5af823cb20d8451db252811371f7e25b1e3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Wed, 6 May 2026 13:29:34 +0000
Subject: [PATCH] perf: eliminate LINQ allocations in IsIgnored hot path
Replace GetAttributes<ConditionBaseAttribute> + GroupBy LINQ pipeline
with direct iteration of the cached Attribute[] array.
Fast path (no ConditionBaseAttribute): zero allocations per call.
Slow path (ConditionBaseAttribute present): one Dictionary instead of
yield iterator + LINQ Lookup internals.
IsIgnored is called twice per test execution (class + method), so for
a suite of 1,000 tests this eliminates ~2,000 short-lived objects in
the common case.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Helpers/AttributeHelpers.cs | 57 ++++++++++++++-----
1 file changed, 43 insertions(+), 14 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs
index a2bbcbe..e522ece 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs+++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/AttributeHelpers.cs@@ -9,27 +9,56 @@ 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)+ Attribute[] attributes = ReflectHelper.Instance.GetCustomAttributesCached(type);++ // Fast path: no ConditionBaseAttribute means nothing is ignored (zero allocations).+ bool hasConditionAttribute = false;+ foreach (Attribute
... (truncated)
π€ This PR was created by Perf Improver, an automated AI assistant focused on performance improvements.
Goal and Rationale
Reduce heap allocations in
AttributeHelpers.IsIgnored, which is called twice per test execution β once for the test class (ClassType.IsIgnored) and once for the test method (MethodInfo.IsIgnored). Allocations here scale linearly with test count.Approach
Before:
Every call allocates:
GetAttributes(even when noConditionBaseAttributeis present)GroupByoperator object (closure + Lookup internals)After:
Direct iteration of the cached
Attribute[]β no iterator, no LINQ operator. TheDictionaryin the slow path is allocated only when aConditionBaseAttributeis present (the uncommon case).Performance Evidence
Methodology: Allocation count per
IsIgnoredcall.ConditionBaseAttribute(common)[Ignore]present on class/methodFor a test suite with 1,000 tests: eliminates ~2,000β4,000 short-lived LINQ objects per run in the typical case.
Trade-offs
GetTestPropertiesAsTraitsandGetTestCategoriesin the same codebase.Dictionaryallocated in the uncommon[Ignore]case replaces a LINQLookup<>β comparable allocation profile.Reproducibility
Test Status
β
MSTestAdapter.PlatformServicesbuilds with 0 warnings, 0 errors on net8.0.Closes #7992, #7993, #8000, #8016, #8028
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)