Skip to content

[Efficiency Improver] perf: eliminate yield-iterator state machine in PropertyBag.OfType(T)()Β #7914

@github-actions

Description

@github-actions

πŸ€– 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

return typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty))
    ? []
    : _property is null ? [] : [.. _property.OfType<TProperty>()];
//                                ^^^^ allocates a state-machine object every call

After

if (typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty)) || _property is null)
    return [];

// Direct linked-list walk β€” no state machine
TProperty? first = default; bool foundAny = false; List<TProperty>? overflow = null;
Property? current = _property;
while (current is 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.

Reproducibility

export PATH="$PWD/.dotnet:$PATH"
dotnet restore test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj -p:TargetFramework=net8.0
dotnet test test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj \
  -p:TargetFramework=net8.0 --no-restore --filter "PropertyBag"

Test Status

βœ… All 12 PropertyBagTests pass on net8.0. Build: 0 errors, 0 warnings.

Generated by Daily Efficiency Improver Β· ● 4.3M Β· β—·


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)
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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions