Skip to content

[efficiency-improver] perf: cache ReflectionTestMethodInfo and GetParameters() across data rows #9593

Description

@github-actions

Goal and Rationale

For data-driven tests using ITestDataSource (e.g. [DynamicData]), the inner execution loop calls ExecuteTestWithDataSourceAsync once per data row. Each call previously created a fresh ReflectionTestMethodInfo wrapper, and each ComputeDefaultDisplayName call on that wrapper invoked _methodInfo.GetParameters() — which always allocates a new ParameterInfo[] array (CLR safety guarantee). This is the same pattern already fixed for TestMethodInfo.ParameterTypes and AssemblyEnumerator.TryUnfoldITestDataSource in PR #9514.

Focus Area

Code-Level Efficiency — unnecessary object creation per data row.

Approach

Two complementary changes:

  1. ReflectionTestMethodInfo.GetParameters() — add a _cachedParameters field; subsequent calls return the cached array instead of delegating to _methodInfo.GetParameters() every time.

  2. TestMethodRunner — add a _cachedReflectionMethodInfo lazy field so the wrapper object is created once per TestMethodRunner instance and reused across all data rows. Both _testMethodInfo.MethodInfo and _test.DisplayName are immutable for the lifetime of a TestMethodRunner, so sharing a single wrapper is safe.

Energy Efficiency Evidence

Proxy metric: heap allocations (fewer allocations = less GC pressure = less CPU energy for garbage collection; also reduces DRAM refresh pressure).

ScenarioBeforeAfter
[DynamicData] test with N=1 row1 ReflectionTestMethodInfo + 1 ParameterInfo[]1 + 1 (same)
[DynamicData] test with N=100 rows100 + 100 = 200 allocations1 + 1 = 2 allocations
[DynamicData] test with N=1000 rows1000 + 1000 = 2000 allocations1 + 1 = 2 allocations

Savings scale linearly with row count. Large data-driven test suites (common in parameterised unit tests and table-driven integration tests) benefit most.

Green Software Foundation context:

  • Hardware Efficiency: Reduces short-lived object churn on the managed heap, improving GC throughput and reducing CPU cycles spent in collection.
  • Software Carbon Intensity (SCI): Fewer allocations per functional unit (one test execution) → lower energy per test run → lower SCI.

Trade-offs

  • Correctness: ReflectionTestMethodInfo wraps an immutable MethodInfo; GetParameters() always returns the same logical data. Caching does not change observable behaviour.
  • Complexity: Adds one ParameterInfo[]? field (8 bytes on 64-bit) to ReflectionTestMethodInfo and one ReflectionTestMethodInfo? field to TestMethodRunner. Trivial cost.
  • MakeGenericMethod: MakeGenericMethod creates a new ReflectionTestMethodInfo with a different underlying MethodInfo, so it correctly starts with a null _cachedParameters. ✅

Reproducibility

# Build the changed projects
./build.sh # installs pinned SDK
.dotnet/dotnet build src/TestFramework/TestFramework/TestFramework.csproj -c Debug
.dotnet/dotnet build src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj -c Debug

For allocation benchmarks: use dotnet-trace or BenchmarkDotNet with [DynamicData] test over 100+ rows and compare Gen0/Gen1 GC counts.

Test Status

Both changed source projects (TestFramework.csproj and MSTestAdapter.PlatformServices.csproj) build cleanly with Build succeeded on the pinned SDK. Full CI will validate across all target frameworks.

🤖 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 Efficiency Improver workflow. · 396.1 AIC · ⌖ 21 AIC · ⊞ 13.6K · [◷]( · )

Add this agentic workflows to your repo

To install this agentic workflow, run

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

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/cache-reflection-method-info-across-data-rows-f161f0f322668db8.

Click here to create the pull request

To fix the permissions issue, go to SettingsActionsGeneral and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ

Show patch preview (99 of 99 lines)
From 351ed720493fd346ec38756bc37caefa3353e1c8 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Fri, 3 Jul 2026 22:13:56 +0000
Subject: [PATCH] perf: cache ReflectionTestMethodInfo and GetParameters()
across data rows
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
For data-driven tests using ITestDataSource (e.g. [DynamicData]), the
execution loop calls ExecuteTestWithDataSourceAsync once per data row.
Each call previously created a fresh ReflectionTestMethodInfo wrapper,
and each ComputeDefaultDisplayName call on that wrapper called
_methodInfo.GetParameters() which always allocates a new ParameterInfo[]
(CLR safety guarantee — same pattern fixed in TestMethodInfo.ParameterTypes
by PR #9514).
Changes:
- ReflectionTestMethodInfo.GetParameters(): add _cachedParameters field,
return cached result on subsequent calls via ??= operator.
- TestMethodRunner: add _cachedReflectionMethodInfo lazy field; reuse the
same wrapper instance across all data rows instead of allocating per row.
Both _testMethodInfo.MethodInfo and _test.DisplayName are immutable for
the lifetime of TestMethodRunner, so sharing is safe.
Impact (proxy metric: heap allocations):
Before: N ReflectionTestMethodInfo + N ParameterInfo[] per data-driven test
After: 1 ReflectionTestMethodInfo + 1 ParameterInfo[] per data-driven test
For 100 rows: ~198 fewer heap allocations per test method execution
This continues the work started in PR #9514 (GetParameters caching in
AssemblyEnumerator and TestMethodInfo.ParameterTypes).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Execution/TestMethodRunner.cs | 15 ++++++++++++---
.../Internal/ReflectionTestMethodInfo.cs | 3 ++-
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.Platfo
... (truncated)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions