Skip to content

[Efficiency Improver] perf: eliminate yield-iterator and GroupBy allocations in IsIgnored() hot path #8159

@Evangelink

Description

@Evangelink

🤖 This PR was created by Daily Efficiency Improver, an automated AI assistant focused on reducing the energy consumption and computational footprint of this repository.

Closes #8143


Goal and Rationale

AttributeExtensions.IsIgnored() is called twice per test execution (class-level + method-level checks in UnitTestRunner.cs). The original implementation always allocated:

  1. A compiler-generated yield-iterator state machine via GetAttributes<ConditionBaseAttribute>() (~48 bytes per call)
  2. A GroupBy Lookup via LINQ GroupBy(), even when the source sequence is empty

For the overwhelming majority of tests — those with no [Ignore] or other ConditionBaseAttribute — both allocations were wasted on every test execution.

Focus Area

Code-Level Efficiency — eliminating unnecessary heap allocations in the test execution hot path.

Approach

Replace GetAttributes() + GroupBy() with a direct walk of GetCustomAttributesCached(), following the same pattern established by PropertyBag.OfType(), GetRetryAttribute(), and TryExecuteFoldedDataDrivenTestsAsync() in this codebase:

Case Before After
No condition attrs (most common) yield state machine + GroupBy Lookup 0 allocations — returns immediately
Single condition attr yield state machine + GroupBy Lookup 0 extra allocations — evaluates directly
Multiple condition attrs yield state machine + GroupBy Lookup List(T) + GroupBy Lookup (same as before, minus state machine)

Energy Efficiency Evidence

Proxy metric: heap allocation count per test run → GC pressure → CPU time in GC → energy consumption

For a 10,000-test suite where no tests have condition attributes (typical):

  • Calls to IsIgnored(): 20,000 (2 per test × 10,000 tests)
  • State machine objects eliminated: ~20,000 × 48 bytes ≈ 960 KB
  • GroupBy Lookup objects eliminated: ~20,000 (one per call on empty sequence)
  • Net GC pressure reduction: ~40,000 objects / ~1 MB per 10,000-test run

Methodology: Allocation sizes from .NET runtime + IL analysis of yield-generated state machines. The GroupBy Lookup is created on first MoveNext() call even on empty sources.

🌱 Green Software Foundation Context

Hardware Efficiency — fewer managed allocations reduce DRAM refresh cycles and GC CPU overhead per unit of test work. The GC cost is proportional to allocation rate; eliminating ~40,000 objects per 10,000-test run directly reduces CPU cycles spent collecting dead objects.

Trade-offs

  • The multi-attribute path uses GroupBy on a List<> (same semantics — minus the yield state machine overhead). No semantic change.
  • Code is longer (~90 lines vs ~30 lines) but follows the same pattern established elsewhere in this codebase.

Reproducibility

# Measure heap allocations with dotnet-trace:
dotnet-trace collect --providers Microsoft-DotNETRuntime:GCAllocationTick -- dotnet test

Test Status

✅ Build: 0 warnings, 0 errors (net9.0).

Local unit test execution is blocked in this CI environment by a package feed issue (AwesomeAssertions.dll path resolution error for MSTestAdapter.PlatformServices.UnitTests). The change is a pure refactoring — semantically equivalent to the original for all three cases. CI will run the full suite.

Generated by Daily Efficiency Improver · ● 12.9M ·


Note

This was originally intended as a pull request, but the git push operation failed.

Workflow Run: View run details and download bundle artifact

The bundle 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 25752925292 -n agent -D /tmp/agent-25752925292

# Fetch the bundle into a local branch
git fetch /tmp/agent-25752925292/aw-efficiency-optimize-isignored-allocs-1778609590.bundle refs/heads/efficiency/optimize-isignored-allocs-1778609590:refs/heads/efficiency/optimize-isignored-allocs-1778609590-612a40a2cdae11a1
git checkout efficiency/optimize-isignored-allocs-1778609590-612a40a2cdae11a1

# Push the branch to origin
git push origin efficiency/optimize-isignored-allocs-1778609590-612a40a2cdae11a1

# Create the pull request
gh pr create --title '[Efficiency Improver] perf: eliminate yield-iterator and GroupBy allocations in IsIgnored() hot path' --base main --head efficiency/optimize-isignored-allocs-1778609590-612a40a2cdae11a1 --repo microsoft/testfx

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions