Skip to content

perf: eliminate LINQ allocations in hot paths #4160

@thomhurst

Description

@thomhurst

Parent Epic

Part of #4159 - Performance Optimization: Hot Path Improvements

Problem

LINQ methods (.Any(), .Where(), .OrderBy(), .Select().ToArray()) allocate iterator objects and intermediate collections on every call. In hot paths executed per-test, this creates GC pressure proportional to test count.

Evidence

File Lines Pattern Impact
TUnit.Engine/Scheduling/ConstraintKeyScheduler.cs 59, 165 .Any() inside lock Per-constrained-test
TUnit.Engine/Scheduling/TestScheduler.cs 174 .OrderBy() in foreach Per-test-group
TUnit.Engine/Services/EventReceiverOrchestrator.cs 248, 262 .OrderBy() during dispatch Per-test-event
TUnit.Engine/Discovery/ReflectionTestDataCollector.cs 361, 490, 522-530 .Where().ToArray() Per-type during discovery
TUnit.Engine/Building/TestBuilder.cs 157, 215, 697, 1108-1110 .Any(), .Select().ToList() Per-test-build
TUnit.Engine/Services/HookCollectionService.cs 555-557, 581-583 .OrderBy().Select().ToList() Per-class hooks

Suggested Approach

Replace LINQ with manual loops. Example transformation:

// Before
canStart = !constraintKeys.Any(key => lockedKeys.Contains(key));

// After  
var canStart = true;
foreach (var key in constraintKeys)
{
    if (lockedKeys.Contains(key)) { canStart = false; break; }
}
// Before
foreach (var kvp in group.Value.OrderBy(t => t.Key))

// After
var sorted = new List<KeyValuePair<int, T>>(group.Value);
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var kvp in sorted)

Verification

  1. Run TUnit.PerformanceBenchmarks at 10k scale before changes
  2. Apply LINQ elimination changes
  3. Run benchmarks again, compare total time and GC counts
  4. Use dotnet-trace allocation profiling to verify reduced allocations

Risks

  • Low risk - straightforward mechanical transformation
  • Ensure loop logic matches LINQ semantics exactly (early exit for .Any(), stable sort for .OrderBy())

Priority

P0 - Quick wins with low risk, high impact on per-test allocations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions