[efficiency-improver] perf: cache MethodInfo.GetParameters() to avoid per-row array allocations in data-driven tests#9514
Open
Evangelink wants to merge 2 commits into
Conversation
…ions in data-driven tests
MethodInfo.GetParameters() returns a fresh ParameterInfo[] copy on every call (CLR
safety guarantee to prevent callers from mutating internal state). For a data-driven
test method with N rows this means:
- During discovery (TryUnfoldITestDataSource): N identical ParameterInfo[] arrays
allocated inside the foreach loop, where only one is ever needed.
- During execution (TestMethodRunner via TestMethodInfo.ParameterTypes): up to 3
arrays per row from three separate property accesses in ExecuteTestWithDataSourceAsync.
Fixes:
1. AssemblyEnumerator.TryUnfoldITestDataSource: hoist GetParameters() before the
foreach loop so the array is computed once and reused for all rows.
2. TestMethodInfo.ParameterTypes: lazy-cache using the C# 14 'field' keyword
(LangVersion=preview) so the array is computed once per TestMethodInfo instance
and all subsequent data-row accesses return the cached reference.
Energy proxy: memory allocation — fewer short-lived heap objects reduces GC
pressure (mark/sweep cycles), which reduces CPU cycles spent in the collector.
For a test with 100 data rows this cuts ParameterInfo[] allocations from ~400 to ~1.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR targets MSTest adapter performance by reducing repeated MethodInfo.GetParameters() calls (which allocate a new ParameterInfo[] on each invocation) in both discovery and execution paths for data-driven tests.
Changes:
- Hoists
GetParameters()out of the per-row discovery loop inAssemblyEnumerator.TryUnfoldITestDataSource. - Lazily caches
GetParameters()inTestMethodInfo.ParameterTypesto avoid repeated allocations during execution.
Show a summary per file
| File | Description |
|---|---|
| src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs | Adds lazy caching for parameter metadata; potential API/behavioral implications due to returning a cached mutable array. |
| src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs | Hoists parameter retrieval outside the data row iteration to avoid per-row allocations during discovery. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Low
Keep the cached ParameterInfo[] for internal call sites (the data-driven hot path uses the concrete TestMethodInfo type), but explicitly implement ITestMethod.ParameterTypes to clone the array. This preserves the previous 'fresh array per call' behavior for external interface consumers and prevents them from mutating the shared cache. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Eliminate redundant
ParameterInfo[]heap allocations in the data-driven test execution and discovery paths.Focus Area
Code-Level Efficiency — reducing unnecessary object creation that causes GC pressure.
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 reflection state. This means every call allocates a new array on the heap, even when the method signature hasn't changed.Two separate call sites in the MSTest adapter were calling
GetParameters()repeatedly for the same method while iterating over data rows:1.
AssemblyEnumerator.TryUnfoldITestDataSource(discovery path)For a test with N data rows, this allocates N identical
ParameterInfo[]objects during test discovery.2.
TestMethodInfo.ParameterTypes(execution path)In
TestMethodRunner.ExecuteTestWithDataSourceAsync,ParameterTypesis accessed up to 3 times per data row:Fix
AssemblyEnumerator.cs: hoistGetParameters()above theforeach— one call before the loop, reused across all rows.TestMethodInfo.cs: use the C# 14fieldkeyword to lazily cache the result once perTestMethodInfoinstance (the backingMethodInfoisget-only and set once in the constructor, so the cache is valid for the lifetime of the object):Energy Efficiency Evidence
ParameterInfo[]allocs — discovery (N rows)ParameterInfo[]allocs — execution (N rows)Proxy metric: heap allocation count. Fewer short-lived heap objects → fewer GC mark/sweep cycles → fewer CPU cycles spent collecting garbage → lower energy per test run.
For a data-driven test with 100 rows, this reduces
ParameterInfo[]allocations from ~400 to 1.Green Software Foundation — Hardware Efficiency: making better use of already-allocated memory by reusing a cached value rather than churning through repeated identical allocations improves work-per-watt.
Trade-offs
None significant. The
fieldkeyword lazy-initialisation is a one-liner that's idiomatic C# 14. The hoistedGetParameters()call is straightforward.MethodInfois get-only onTestMethodInfo, so the cached value is permanently valid.Reproducibility
Any test suite with
[DynamicData]or customITestDataSourcetest methods will exercise both paths. Run./build.sh -pack -test -integrationTestto exercise acceptance tests that use data-driven tests.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