Skip to content

[perf-improver] perf: pass cached ParameterTypes to GetInvokeResultAsync to avoid per-invocation GetParameters() allocation #9659

Description

@github-actions

Goal

Eliminate one (or two for generic methods) ParameterInfo[] heap allocations per test invocation in the MSTest execution hot path.

Background

MethodInfo.GetParameters() is a CLR-mandated copy-on-every-call API: the runtime keeps an internal ParameterInfo[] but returns a fresh copy to each caller to prevent external mutation of internal state. This means every call allocates a new array on the heap, even when the method signature hasn't changed between invocations.

TestMethodInfo.ParameterTypes already caches this result via field ??= MethodInfo.GetParameters() (introduced in #9514). However, the two GetInvokeResultAsync call sites in TestMethodInfo.Execution.cs passed only the raw MethodInfo:

// Both call sites, called once per test execution:Task?invokeResult=MethodInfo.GetInvokeResultAsync(_classInstance,arguments);

Inside GetInvokeResultAsync, line 121 then called:

ParameterInfo[]?methodParameters=methodInfo.GetParameters();// fresh alloc every call

For generic test methods, ConstructGenericMethod (called from the same method) called GetParameters() again — a second allocation for the same data.

Approach

  1. Add a GetInvokeResultAsync overload that accepts a pre-cached ParameterInfo[] parameter.
  2. Keep the existing params overload as a thin wrapper that calls GetParameters() once and delegates — all non-hot-path callers (assembly init/cleanup, class init/cleanup, TestInitialize/TestCleanup) are unchanged.
  3. Update ConstructGenericMethod's signature to accept the ParameterInfo[] instead of calling GetParameters() internally.
  4. Update the two hot-path call sites in TestMethodInfo.Execution.cs to pass the cached ParameterTypes property.

Performance Evidence

ScenarioBeforeAfter
Standard test (non-generic)1 ParameterInfo[] alloc per invocation0 (uses cached ParameterTypes)
Generic test method2 ParameterInfo[] allocs per invocation0 (both sites use cached value)

For a suite with N tests, this eliminates N–2N short-lived heap allocations. Fewer short-lived objects → reduced GC pressure → lower pause frequency.

The ParameterTypes cache is valid for the lifetime of the TestMethodInfo instance: MethodInfo is get-only and set once in the constructor.

Trade-offs

Minimal. The additional overload is a 1-line delegation. All callers not on the hot path continue to use the original params overload unchanged. The correctness argument is straightforward: TestMethodInfo.ParameterTypes caches this.MethodInfo.GetParameters() and GetInvokeResultAsync is called with this.MethodInfo, so the cached value is identical to what GetParameters() would return inside the method.

Reproducibility

Any test suite exercises both paths. For direct allocation measurement, run BenchmarkDotNet on a parameterised test method:

./build.sh -pack
# Then run the performance runner:
.dotnet/dotnet run --project test/Performance/MSTest.Performance.Runner -- execute --pipelineNameFilter "*PlainProcess*"

Test Status

./build.sh -test — all unit tests pass (net8.0 + net9.0).

🤖 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. · 476.6 AIC · ⌖ 16.5 AIC · ⊞ 12.5K · [◷]( · )

Add this agentic workflows to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/perf-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 perf-assist/cache-invoke-params-e57ab13bd6b76cab.

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 (102 of 102 lines)
From 3c9ccc0f85548d0ed38022f63f30a276e67627da Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Mon, 6 Jul 2026 14:58:05 +0000
Subject: [PATCH] perf: pass cached ParameterTypes to GetInvokeResultAsync to
avoid per-invocation GetParameters() allocation
MethodInfo.GetParameters() always returns a fresh ParameterInfo[] copy on
every call. TestMethodInfo.ParameterTypes already caches this via 'field ??='
lazy initialisation (added in #9514).
However, the two GetInvokeResultAsync call sites in TestMethodInfo.Execution.cs
passed only the raw MethodInfo, causing GetInvokeResultAsync to call
GetParameters() internally on every single test invocation.
For a suite with N tests this eliminates N ParameterInfo[] allocations. For
generic test methods the saving doubles: ConstructGenericMethod previously
called GetParameters() again on the same MethodInfo.
Changes:
- Add GetInvokeResultAsync overload that accepts a ParameterInfo[] directly.- Keep the params overload as a thin wrapper (calls GetParameters() once,
delegates to the new overload); all non-hot-path callers are unchanged.
- Update ConstructGenericMethod signature to accept ParameterInfo[] instead
of calling GetParameters() internally.
- Update the two hot-path call sites in TestMethodInfo.Execution.cs to pass
the already-cached ParameterTypes property.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Execution/TestMethodInfo.Execution.cs | 4 ++--
.../Extensions/MethodInfoExtensions.cs | 10 ++++++----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs
index 6515ea5..1d3f7d5 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execu
... (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