Skip to content

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

Description

@github-actions

Goal and Rationale

Eliminate redundant ParameterInfo[] allocations in the data-driven test hot path. MethodInfo.GetParameters() is a CLR method that always returns a freshly allocated array (CLR safety guarantee). For a test with N data rows every row triggers one or more such allocations, creating O(N) heap pressure that is avoidable.

Focus area: Code-Level Efficiency — unnecessary object creation on a per-row hot path.

Approach

Three targeted changes:

1. ReflectionTestMethodInfo.GetParameters() — cache result

// BeforepublicoverrideParameterInfo[]GetParameters()=>_methodInfo.GetParameters();// AfterprivateParameterInfo[]?_parameters;publicoverrideParameterInfo[]GetParameters()=>_parameters??=_methodInfo.GetParameters();

ReflectionTestMethodInfo wraps an immutable MethodInfo and is used to compute row display names. The parameter list cannot change between calls, so caching is safe.

2. TestMethodInfo.ResolveArguments() — use cached ParameterTypes

// BeforeParameterInfo[]parametersInfo=MethodInfo.GetParameters();// AfterParameterInfo[]parametersInfo=ParameterTypes;

PR #9514 added ParameterTypes (lazy-cached MethodInfo.GetParameters()) to TestMethodInfo, but ResolveArguments() was left calling the raw method, bypassing the cache. This fixes the oversight.

3. TestMethodRunner._cachedReflectionMethodInfo — reuse wrapper across rows

// Before (in ExecuteTestWithDataSourceAsync — called once per row)varreflectionMethodInfo=newReflectionTestMethodInfo(_testMethodInfo.MethodInfo,_test.DisplayName);// After_cachedReflectionMethodInfo??=newReflectionTestMethodInfo(_testMethodInfo.MethodInfo,_test.DisplayName);

_testMethodInfo.MethodInfo and _test.DisplayName are both immutable for the TestMethodRunner lifetime, so the wrapper can safely be cached as a field and shared across all data rows.

Energy Efficiency Evidence

Proxy metric used: Heap allocation count — directly maps to GC pressure and the CPU energy spent on GC collection cycles.

ScenarioBeforeAfter
ReflectionTestMethodInfo instances per data-driven testN1
ParameterInfo[] from GetParameters() in display-name pathN1
ParameterInfo[] from GetParameters() in ResolveArguments()N1

N = number of data rows. For a [DynamicData] test providing 100 rows, this reduces from ~300 short-lived heap objects (3 × N) to 3 total — a 99% reduction in these allocations.

Reproducibility: Run any data-driven MSTest test under a profiler (dotnet-trace / BenchmarkDotNet with MemoryDiagnoser) to observe allocation counts per row before and after this change.

Green Software Foundation Context

Hardware Efficiency / Demand Shaping: Reduced GC frequency means the runtime reclaims CPU resources for actual test execution rather than collection bookkeeping. On energy-proportional hardware, lower background CPU demand translates directly to lower power draw during test runs.

Trade-offs

  • The cached ParameterInfo[] in ReflectionTestMethodInfo must not be mutated by callers. All existing call sites read the array without mutation (display-name formatting, parameter-count checks), so this is safe. The existing ITestMethod.ParameterTypes interface contract (which clones the array for external consumers) is unchanged.
  • The _cachedReflectionMethodInfo field is null for non-data-driven tests, adding 8 bytes to TestMethodRunner instances with no behavioural change.

Test Status

  • ./build.sh — build succeeded, 0 warnings
  • ./build.sh -test — all unit tests passed (MTP-hosted suite)
  • i️ MSTestAdapter-internal tests not run locally (CI-only; handled separately per repo convention)

🤖 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. · 177.3 AIC · ⌖ 21.4 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-getparameters-across-data-rows-31a88443c00219eb.

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 (110 of 110 lines)
From 4a4f8ce1bae6cea21822017e340049cf98731b51 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Sat, 4 Jul 2026 22:10:21 +0000
Subject: [PATCH] perf: cache GetParameters() and ReflectionTestMethodInfo
across data rows
Three micro-optimisations that eliminate redundant ParameterInfo[] allocations
for every row in a data-driven test run:
1. ReflectionTestMethodInfo.GetParameters():
MethodInfo.GetParameters() allocates a fresh ParameterInfo[] on every call
(CLR safety guarantee). Cache with _parameters ??= _methodInfo.GetParameters()
so all callers on the same wrapper share a single allocation.
2. TestMethodInfo.ResolveArguments():
Was calling MethodInfo.GetParameters() directly, bypassing the cached
TestMethodInfo.ParameterTypes property added in #9514. Switch to ParameterTypes.
3. TestMethodRunner._cachedReflectionMethodInfo:
ExecuteTestWithDataSourceAsync() created a new ReflectionTestMethodInfo per
data row. Since both wrapped MethodInfo and DisplayName are immutable for the
TestMethodRunner lifetime, cache the wrapper so N rows share one instance.
Impact (proxy metric: heap allocations per data-driven test execution):
- Before: N ReflectionTestMethodInfo + N ParameterInfo[] from GetParameters()
in the display-name path, plus N ParameterInfo[] in ResolveArguments()
- After: 1 ReflectionTestMethodInfo + 1 ParameterInfo[] shared across N rows
(N = number of data rows supplied by the ITestDataSource/DynamicData attribute)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Execution/TestMethodInfo.ArgumentResolution.cs | 2 +-
.../Execution/TestMethodRunner.cs | 12 +++++++++---
.../Internal/ReflectionTestMethodInfo.cs | 6 +++++-
3 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.ArgumentResolution.cs b/src/Adapter/MSTestAdapter.PlatformS
... (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