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 focused on reducing energy consumption and computational footprint.
Goal and Rationale
PropertyBag.OfType<TProperty>() is called on every TestNodeUpdateMessage in multiple consumers (TerminalOutputDevice, DotnetTestDataConsumer, OpenTelemetryResultHandler, etc.). The previous implementation delegated to Property.OfType<TProperty>(), which uses yield return β allocating a heap-resident state-machine object on every call, even when the result is empty (the common case for types like FileArtifactProperty).
Focus Area
Code-Level Efficiency β unnecessary object creation in a hot path.
Approach
Replace [.. _property.OfType<TProperty>()] (which invokes a yield return iterator) with a direct while-loop walk of the linked list β the same pattern already applied to SingleOrDefault<T>() and Single<T>() in PR #7857.
Before
returntypeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty))?[]:_propertyisnull?[]:[.. _property.OfType<TProperty>()];// ^^^^ allocates a state-machine object every call
After
if(typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty))||_propertyisnull)return[];// Direct linked-list walk β no state machineTProperty?first=default;boolfoundAny=false;List<TProperty>?overflow=null;Property?current=_property;while(currentis not null){/* pattern match + collect */current=current.Next;}return!foundAny?[]: overflow is not null?[.. overflow]:[first!];
Energy Efficiency Evidence
Proxy metric: heap allocations per OfType<T>() call (fewer allocations β less GC pressure β less CPU time spent in garbage collection β lower energy draw).
Case
Before
After
_property is null
[] (no state machine)
[] (unchanged)
Non-null _property, no match (common: FileArtifactProperty)
1 state-machine object
0
Non-null _property, 1 match
1 state-machine object + 1 array
1 array
Non-null _property, N matches
1 state-machine + 1 array
1 List + 1 array (same)
In a test run with 10 000 test nodes and no file artifacts, this eliminates ~10 000 state-machine allocations (~40β80 bytes each on .NET heap).
Rationale β energy link: GC pause time is proportional to allocation rate. Reducing short-lived allocations on the test-node hot path lowers GC overhead, reducing CPU cycles and energy consumed per test run.
π± GSF principle: Hardware Efficiency β eliminate GC pressure from transient heap objects in the BFS/test-node processing hot path.
Trade-offs
Slightly more code than the single-line original. The overflow list path (β₯2 matching properties) trades one yield state machine for one List<T> allocation β same net cost for that rare case.
Readability is similar to the already-accepted SingleOrDefault<T>() pattern in the same file.
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/propertybag-oftype-no-state-machine-8f9c5bd2284aea80.
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 (70 of 70 lines)
From 22907c7d9f3dd77bc189b6900ffdc5edb9e68a32 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Wed, 29 Apr 2026 04:34:21 +0000
Subject: [PATCH] perf: eliminate yield-iterator state machine in
PropertyBag.OfType<T>()
Replace the yield-based iterator from Property.OfType<TProperty>() with
a direct linked-list walk inside PropertyBag.OfType<TProperty>().
Before: _property.OfType<TProperty>() (yield return) allocates one state-
machine heap object per call, even when no matching property is found.
After: direct while-loop walk; no state-machine allocated; returns [] for
the common case (no matching property) with zero heap allocations beyond
the empty array constant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Messages/PropertyBag.cs | 33 +++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs b/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs
index 36f3a940d..01c98c6b0 100644
--- a/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs+++ b/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs@@ -274,9 +274,38 @@ public TProperty[] OfType<TProperty>()
}
// We don't want to allocate an array if we know that we're looking for a TestNodeStateProperty
- return typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty))+ if (typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty)) || _property is null)+ {+ return [];+ }++ // Direct linked-list walk: avoids allocating a yield-iterator state machine+ // (the original code called _property.OfType<TProperty>() which uses yield return).+ TProperty? first = default;+ bool foundAny = false;+ List<TProperty>? overflow = null;+ Property? current = _property;+ whi
... (truncated)
π€ Daily Efficiency Improver β automated AI assistant focused on reducing energy consumption and computational footprint.
Goal and Rationale
PropertyBag.OfType<TProperty>()is called on everyTestNodeUpdateMessagein multiple consumers (TerminalOutputDevice,DotnetTestDataConsumer,OpenTelemetryResultHandler, etc.). The previous implementation delegated toProperty.OfType<TProperty>(), which usesyield returnβ allocating a heap-resident state-machine object on every call, even when the result is empty (the common case for types likeFileArtifactProperty).Focus Area
Code-Level Efficiency β unnecessary object creation in a hot path.
Approach
Replace
[.. _property.OfType<TProperty>()](which invokes ayield returniterator) with a directwhile-loop walk of the linked list β the same pattern already applied toSingleOrDefault<T>()andSingle<T>()in PR #7857.Before
After
Energy Efficiency Evidence
Proxy metric: heap allocations per
OfType<T>()call (fewer allocations β less GC pressure β less CPU time spent in garbage collection β lower energy draw)._property is null[](no state machine)[](unchanged)_property, no match (common:FileArtifactProperty)_property, 1 match_property, N matchesIn a test run with 10 000 test nodes and no file artifacts, this eliminates ~10 000 state-machine allocations (~40β80 bytes each on .NET heap).
Rationale β energy link: GC pause time is proportional to allocation rate. Reducing short-lived allocations on the test-node hot path lowers GC overhead, reducing CPU cycles and energy consumed per test run.
π± GSF principle: Hardware Efficiency β eliminate GC pressure from transient heap objects in the BFS/test-node processing hot path.
Trade-offs
overflowlist path (β₯2 matching properties) trades oneyieldstate machine for oneList<T>allocation β same net cost for that rare case.SingleOrDefault<T>()pattern in the same file.Reproducibility
Test Status
β All 12
PropertyBagTestspass on net8.0. Build: 0 errors, 0 warnings.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/propertybag-oftype-no-state-machine-8f9c5bd2284aea80.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 (70 of 70 lines)