You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
Add a GetInvokeResultAsync overload that accepts a pre-cached ParameterInfo[] parameter.
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.
Update ConstructGenericMethod's signature to accept the ParameterInfo[] instead of calling GetParameters() internally.
Update the two hot-path call sites in TestMethodInfo.Execution.cs to pass the cached ParameterTypes property.
Performance Evidence
Scenario
Before
After
Standard test (non-generic)
1 ParameterInfo[] alloc per invocation
0 (uses cached ParameterTypes)
Generic test method
2 ParameterInfo[] allocs per invocation
0 (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.
To fix the permissions issue, go to Settings → Actions → General 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)
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 internalParameterInfo[]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.ParameterTypesalready caches this result viafield ??= MethodInfo.GetParameters()(introduced in #9514). However, the twoGetInvokeResultAsynccall sites inTestMethodInfo.Execution.cspassed only the rawMethodInfo:Inside
GetInvokeResultAsync, line 121 then called:For generic test methods,
ConstructGenericMethod(called from the same method) calledGetParameters()again — a second allocation for the same data.Approach
GetInvokeResultAsyncoverload that accepts a pre-cachedParameterInfo[]parameter.paramsoverload as a thin wrapper that callsGetParameters()once and delegates — all non-hot-path callers (assembly init/cleanup, class init/cleanup, TestInitialize/TestCleanup) are unchanged.ConstructGenericMethod's signature to accept theParameterInfo[]instead of callingGetParameters()internally.TestMethodInfo.Execution.csto pass the cachedParameterTypesproperty.Performance Evidence
ParameterInfo[]alloc per invocationParameterTypes)ParameterInfo[]allocs per invocationFor a suite with N tests, this eliminates N–2N short-lived heap allocations. Fewer short-lived objects → reduced GC pressure → lower pause frequency.
The
ParameterTypescache is valid for the lifetime of theTestMethodInfoinstance:MethodInfoisget-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
paramsoverload unchanged. The correctness argument is straightforward:TestMethodInfo.ParameterTypescachesthis.MethodInfo.GetParameters()andGetInvokeResultAsyncis called withthis.MethodInfo, so the cached value is identical to whatGetParameters()would return inside the method.Reproducibility
Any test suite exercises both paths. For direct allocation measurement, run BenchmarkDotNet on a parameterised test method:
Test Status
./build.sh -test— all unit tests pass (net8.0 + net9.0).Add this agentic workflows to your repo
To install this agentic workflow, run
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 Settings → Actions → General and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ
Show patch preview (102 of 102 lines)