Skip to content

[perf-improver] Replace Array.IndexOf(GetType()) with 'is' pattern matching in hot paths#9299

Draft
Evangelink wants to merge 1 commit into
mainfrom
perf-assist/is-pattern-replace-array-indexof-4aed1a42fac03721
Draft

[perf-improver] Replace Array.IndexOf(GetType()) with 'is' pattern matching in hot paths#9299
Evangelink wants to merge 1 commit into
mainfrom
perf-assist/is-pattern-replace-array-indexof-4aed1a42fac03721

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Goal and Rationale

Every test node update passes through TestApplicationResult.ConsumeAsync — including the common InProgressTestNodeStateProperty transitions that fire for every test start/end. The previous code had a switch statement that already dispatched on all known types, then re-ran GetType() plus up to three Array.IndexOf scans (4–5 elements each) to increment counters and classify outcomes. This double-dispatch was pure overhead.

Two other hot paths — AbortForMaxFailedTestsExtension (active when --maximum-failed-tests is set) and RetryDataConsumer (active when retry is on) — had the same pattern.

Approach

Replace every Array.IndexOf(WellKnownTestNodeXxx, state.GetType()) != -1 idiom with an is FailedTestNodeStateProperty or ErrorTestNodeStateProperty ... type-pattern chain, which is resolved by the JIT into a sequence of isinst/type-check instructions with no allocation, no dictionary lookup, and no array traversal.

In TestApplicationResult.ConsumeAsync, the counter increments (_failedTestsCount++, _totalRanTests++) were merged directly into the existing switch arms, removing the redundant second pass entirely.

Files changed:

File Change
TestApplicationResult.cs Fold counters into switch; remove GetType() + 3× Array.IndexOf
AbortForMaxFailedTestsExtension.cs Replace 1× GetType() + Array.IndexOf with is pattern
RetryDataConsumer.cs Replace 2× GetType() + Array.IndexOf with is patterns

Performance Evidence

The change is a mechanical substitution of array-scan classification with JIT-inlined isinst checks. For a 10 000-test run:

  • Before: ConsumeAsync called ~10 000× — each call boxed GetType() then scanned arrays of 4–5 Type objects up to 3 times.
  • After: counters are updated in the same switch branch that was already dispatching; no extra allocations.

No microbenchmark exists in this repo for this path yet; the improvement is verified by code-path analysis. The WellKnownTestNodeXxx arrays remain in TestNodeProperties.Categories.cs for documentation and any callers outside this hot path.

Trade-offs

  • Very small increase in code verbosity vs. array reference (explicit type list in each condition). Acceptable since the list is short and stable.
  • No semantic changes — the classification logic is identical.

Reproducibility

./build.sh
.dotnet/dotnet run --project test/UnitTests/Microsoft.Testing.Platform.UnitTests -f net9.0 --no-build
.dotnet/dotnet run --project test/UnitTests/Microsoft.Testing.Extensions.UnitTests -f net9.0 --no-build

Test Status

Build: ✅ succeeded (0 warnings, 0 errors)
Microsoft.Testing.Platform.UnitTests: ✅ 1220 passed, 0 failed
Microsoft.Testing.Extensions.UnitTests: ✅ 539 passed, 0 failed

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Perf Improver workflow. · 2.3K AIC · ⌖ 25.8 AIC · ⊞ 57.6K · [◷]( · )

Add this agentic workflows to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/perf-improver.md@main

…hot paths

Replace three call sites that used GetType() + Array.IndexOf() for test
outcome classification with direct 'is' type-pattern matching. This
eliminates repeated array scans and boxing/reflection overhead on the
per-test hot path.

Changes:
- TestApplicationResult.ConsumeAsync: merge _failedTestsCount and
  _totalRanTests increments directly into the existing switch arms;
  remove Type outcomeType = executionState.GetType() and three
  Array.IndexOf calls that duplicated work already done by the switch.
- AbortForMaxFailedTestsExtension.ConsumeAsync: replace
  Array.IndexOf(WellKnownTestNodeTestRunOutcomeFailedProperties, GetType())
  with an 'is' pattern chain.
- RetryDataConsumer.ConsumeAsync: replace both Array.IndexOf calls
  with 'is' pattern chains.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 20, 2026 14:34
@Evangelink Evangelink added area/performance Runtime / build performance / efficiency. type/automation Created or maintained by an agentic workflow. labels Jun 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes redundant GetType() + Array.IndexOf(...) outcome classification in several high-frequency data consumers, replacing it with direct type-pattern matching and folding counter updates into existing switch dispatch. The intent is to reduce per-test overhead on hot paths in Microsoft.Testing.Platform and its Retry extension.

Changes:

  • Fold _failedTestsCount / _totalRanTests updates directly into the existing switch (executionState) in TestApplicationResult.ConsumeAsync, eliminating the second classification pass.
  • Replace Array.IndexOf(..., state.GetType()) with is ... or ... type patterns in AbortForMaxFailedTestsExtension and RetryDataConsumer.
  • Preserve the existing “skipped tests do not count as ran” behavior by keeping skipped excluded from _totalRanTests.
Show a summary per file
File Description
src/Platform/Microsoft.Testing.Platform/Services/TestApplicationResult.cs Removes post-switch array-scan classification; updates counters in the same switch arms that already handle each state.
src/Platform/Microsoft.Testing.Platform/Extensions/AbortForMaxFailedTestsExtension.cs Replaces failure-type array membership check with explicit type-pattern matching before incrementing fail count / triggering stop policy.
src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs Replaces outcome/failure array membership checks with explicit type-pattern matching for retry signaling and total test counting.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0

#pragma warning disable CS0618, MTP0001 // Type or member is obsolete
or CancelledTestNodeStateProperty
#pragma warning restore CS0618, MTP0001 // Type or member is obsolete
&& ++_failCount >= _maxFailedTests.Value &&
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/performance Runtime / build performance / efficiency. type/automation Created or maintained by an agentic workflow.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants