Summary
TestRunner allocates a TaskCompletionSource<bool> and inserts into a dedup dictionary for every single test — to support test dependency recursion. But the vast majority of tests have zero dependencies, so this allocation and dictionary growth is pure waste. Estimated 1 TCS + 1 dict entry avoided per test (~1K allocations for a 1K-test suite).
Evidence
TUnit.Engine/Scheduling/TestRunner.cs:45-89 — unconditional TCS + _executingTests[testId] = tcs.
Trace: __Canon].GetOrAdd(...) 1.36% inclusive / 1.08% self — partly this dedup dict.
Proposed fix
- Skip the TCS + dict entirely when
test.Dependencies.Count == 0.
- Only allocate on first entry into a dependency-bearing call path.
- Alternative: replace with
ConcurrentDictionary<string, Task> storing the execution Task directly via GetOrAdd, eliminating the TCS and WrapAsync layer.
Expected impact
- 1013 TCS + dict entries avoided on the profile workload.
- Measurable dent in
GetOrAdd 1.08% exclusive.
- Reduced GC pressure on large suites.
Summary
TestRunnerallocates aTaskCompletionSource<bool>and inserts into a dedup dictionary for every single test — to support test dependency recursion. But the vast majority of tests have zero dependencies, so this allocation and dictionary growth is pure waste. Estimated 1 TCS + 1 dict entry avoided per test (~1K allocations for a 1K-test suite).Evidence
TUnit.Engine/Scheduling/TestRunner.cs:45-89— unconditional TCS +_executingTests[testId] = tcs.Trace:
__Canon].GetOrAdd(...)1.36% inclusive / 1.08% self — partly this dedup dict.Proposed fix
test.Dependencies.Count == 0.ConcurrentDictionary<string, Task>storing the execution Task directly viaGetOrAdd, eliminating the TCS andWrapAsynclayer.Expected impact
GetOrAdd1.08% exclusive.