Skip to content

perf: CreateExecutableTestFactory creates new lambdas on every property access #4281

@thomhurst

Description

@thomhurst

Summary

The CreateExecutableTestFactory property getter in TestMetadata<T> creates new lambda closures every time it is accessed, causing unnecessary heap allocations and GC pressure.

Evidence

From dotnet-trace profiling of the TUnit.PerformanceBenchmarks project (3700+ tests):

  • <get_CreateExecutableTestFactory>b__2 shows 5.41% inclusive CPU time
  • <set_InstanceFactory>b__0 shows 2.2% inclusive CPU time

Root Cause

In TUnit.Core/TestMetadata\1.cs` lines 62-116, the property getter creates new lambdas on every access:

public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecutableTest> CreateExecutableTestFactory
{
    get
    {
        if (InstanceFactory != null && InvokeTypedTest != null)
        {
            return (context, metadata) =>  // NEW LAMBDA EVERY ACCESS!
            {
                var typedMetadata = (TestMetadata<T>)metadata;
                Func<TestContext, Task<object>> createInstance = async testContext => { ... };
                Func<object, object?[], TestContext, CancellationToken, Task> invokeTest = async (...) => { ... };
                return new ExecutableTest(createInstance, invokeTest) { ... };
            };
        }
        throw new InvalidOperationException(...);
    }
}

Suggested Fix

Cache the delegate after first creation:

private Func<ExecutableTestCreationContext, TestMetadata, AbstractExecutableTest>? _cachedExecutableTestFactory;

public override Func<ExecutableTestCreationContext, TestMetadata, AbstractExecutableTest> CreateExecutableTestFactory
{
    get
    {
        if (_cachedExecutableTestFactory != null)
            return _cachedExecutableTestFactory;

        if (InstanceFactory != null && InvokeTypedTest != null)
        {
            _cachedExecutableTestFactory = (context, metadata) => { ... };
            return _cachedExecutableTestFactory;
        }
        throw new InvalidOperationException(...);
    }
}

Impact

  • Reduces heap allocations per test by eliminating redundant lambda creation
  • Expected improvement: Noticeable reduction in GC pressure for large test suites
  • Affects every test execution path

Related Files

  • TUnit.Core/TestMetadata\1.cs`

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions