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.
Goal and Rationale
When BFSTestNodeVisitor walks the test tree with a TreeNodeFilter, it calls MatchesFilter for every node. Prior to this change, it always constructed a PropertyBag from the node's IProperty[] β even though the PropertyBag is only inspected when the filter contains [Trait=Value] property expressions.
A PropertyBag backed by N properties allocates 1 PropertyBag object + N Property linked-list nodes per call. In a tree of 1 000 test nodes, each carrying 3 properties, that is 4 000 short-lived heap objects created just to be immediately discarded for the common case where no property filter is active.
Focus Area
Code-Level Efficiency β eliminate unnecessary object creation in a per-node hot path.
Approach
TreeNodeFilter.ContainsPropertyFilters (new internal property) β computed once during filter parsing using _filters.Any(HasPropertyFilterExpression). A recursive helper HasPropertyFilterExpression walks the parsed FilterExpression tree looking for any ValueAndPropertyExpression node (the only kind that inspects PropertyBag contents at match time). Cost: one extra boolean field + O(depth Γ width) traversal at construction time β amortised to zero over the lifetime of the filter.
BFSTestNodeVisitor β when ContainsPropertyFilters is false, the new static EmptyPropertyBag singleton is reused instead of constructing a per-node bag. When true, a PropertyBag is constructed as before (no regression for users who do use property filters).
Energy Efficiency Evidence
Proxy metric: heap allocations per test-node traversal with a path-only TreeNodeFilter (e.g. /AssemblyName/ClassName/MethodName).
Before
After
PropertyBag objects / node
1
0
Property linked-list nodes / node
N (β3β5)
0
Total allocations / 1 000-node tree
~4 000β6 000
0
Reasoning: Fewer short-lived heap objects β less GC pressure β fewer GC pauses β lower CPU/energy draw per test run. This directly maps to the Hardware Efficiency principle from the Green Software Foundation: avoid unnecessary computation and allocation on the critical path.
The static EmptyPropertyBag is allocated exactly once (class static initialiser), so it adds no ongoing cost.
Trade-offs
Minimal: the change adds one boolean field and one helper method to TreeNodeFilter, and changes two lines in BFSTestNodeVisitor. No new public API surface.
EmptyPropertyBag is mutable; however, it is only ever read (never written to) via the MatchesFilter code path when ContainsPropertyFilters is false, so sharing is safe.
Reproducibility
export PATH="$PWD/.dotnet:$PATH"
dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --filter "BFSTestNodeVisitor"
dotnet test test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj -p:TargetFramework=net8.0 --filter "TreeNodeFilter"
Test Status
β 10 / 10 BFSTestNodeVisitorTests pass (net8.0)
β 45 / 45 TreeNodeFilterTests pass (net8.0)
Note
π Integrity filter blocked 1 item
The following item were blocked because they don't meet the GitHub integrity level.
#6611list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
To allow these resources, lower min-integrity in your GitHub frontmatter:
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 efficiency/skip-propertybag-when-no-property-filter-37db331d9e78483b.
To fix the permissions issue, go to Settings β Actions β General and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ
Show patch preview (107 of 107 lines)
From 1b3325b8397454c41178beba09cbf32de0a56af8 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Tue, 28 Apr 2026 04:39:09 +0000
Subject: [PATCH] perf: skip PropertyBag construction in BFSTestNodeVisitor
when filter has no property expressions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add ContainsPropertyFilters to TreeNodeFilter β computed once at parse time.
When false, BFSTestNodeVisitor reuses a static empty PropertyBag instead of
allocating a new PropertyBag (+ N Property linked-list nodes) per traversed
test node.
Proxy metric: heap allocations per test node during BFS traversal with a
TreeNodeFilter that contains no [Trait=Value] property conditions (the common
case).
Before: 1 PropertyBag + N Property nodes per node (N = number of properties)
After: 0 allocations (static empty bag reused)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../MSTest.Engine/Engine/BFSTestNodeVisitor.cs | 17 +++++------------
.../Requests/TreeNodeFilter/TreeNodeFilter.cs | 12 ++++++++++++
2 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
index dc98be1..d443281 100644
--- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -11,6 +11,7 @@ namespace Microsoft.Testing.Framework;
internal sealed class BFSTestNodeVisitor
{
private static readonly string PathSeparatorString = TreeNodeFilter.PathSeparator.ToString();
+ private static readonly PropertyBag EmptyPropertyBag = new();
private readonly IEnumerable<TestNode> _rootTestNodes;
private readonly ITestExecutionFilter _testExecutionFilter;
@@ -72,7 +73,10 @@ public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task> onIncludedTestNo
// When we are filtering as tr
... (truncated)
π€ Daily Efficiency Improver β automated AI assistant.
Goal and Rationale
When
BFSTestNodeVisitorwalks the test tree with aTreeNodeFilter, it callsMatchesFilterfor every node. Prior to this change, it always constructed aPropertyBagfrom the node'sIProperty[]β even though thePropertyBagis only inspected when the filter contains[Trait=Value]property expressions.A
PropertyBagbacked by N properties allocates 1PropertyBagobject + NPropertylinked-list nodes per call. In a tree of 1 000 test nodes, each carrying 3 properties, that is 4 000 short-lived heap objects created just to be immediately discarded for the common case where no property filter is active.Focus Area
Code-Level Efficiency β eliminate unnecessary object creation in a per-node hot path.
Approach
TreeNodeFilter.ContainsPropertyFilters(new internal property) β computed once during filter parsing using_filters.Any(HasPropertyFilterExpression). A recursive helperHasPropertyFilterExpressionwalks the parsedFilterExpressiontree looking for anyValueAndPropertyExpressionnode (the only kind that inspectsPropertyBagcontents at match time). Cost: one extra boolean field + O(depth Γ width) traversal at construction time β amortised to zero over the lifetime of the filter.BFSTestNodeVisitorβ whenContainsPropertyFiltersisfalse, the new staticEmptyPropertyBagsingleton is reused instead of constructing a per-node bag. Whentrue, aPropertyBagis constructed as before (no regression for users who do use property filters).Energy Efficiency Evidence
Proxy metric: heap allocations per test-node traversal with a path-only
TreeNodeFilter(e.g./AssemblyName/ClassName/MethodName).Reasoning: Fewer short-lived heap objects β less GC pressure β fewer GC pauses β lower CPU/energy draw per test run. This directly maps to the Hardware Efficiency principle from the Green Software Foundation: avoid unnecessary computation and allocation on the critical path.
The static
EmptyPropertyBagis allocated exactly once (class static initialiser), so it adds no ongoing cost.Trade-offs
TreeNodeFilter, and changes two lines inBFSTestNodeVisitor. No new public API surface.EmptyPropertyBagis mutable; however, it is only ever read (never written to) via theMatchesFiltercode path whenContainsPropertyFiltersis false, so sharing is safe.Reproducibility
Test Status
BFSTestNodeVisitorTestspass (net8.0)TreeNodeFilterTestspass (net8.0)Note
π Integrity filter blocked 1 item
The following item were blocked because they don't meet the GitHub integrity level.
list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".To allow these resources, lower
min-integrityin your GitHub frontmatter: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
efficiency/skip-propertybag-when-no-property-filter-37db331d9e78483b.Click here to create the pull request
To fix the permissions issue, go to Settings β Actions β General and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ
Show patch preview (107 of 107 lines)