Remove all allocations when resolving and make it even faster#189
Remove all allocations when resolving and make it even faster#189
Conversation
…dd a benchmark for constructor resolution
There was a problem hiding this comment.
Pull request overview
This PR optimizes the reflection-based fallback in ProjectionExpressionResolver by caching fully-resolved LambdaExpression instances (instead of rebuilding them per call), and extends the benchmarks to include constructor resolution so registry vs. reflection performance can be compared more directly.
Changes:
- Replace the reflection “factory delegate” cache with a
LambdaExpressioncache to eliminate repeated expression-tree construction. - Switch the reflection path to invoke the generated
Expressionmethod once and reuse the resulting expression instance. - Add a projectable copy-constructor and benchmark coverage for constructor resolution (registry and reflection paths).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs | Cache resolved reflection expressions (and a null sentinel) instead of caching compiled factories. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/Helpers/TestEntity.cs | Add a projectable copy constructor (and explicit parameterless ctor) for benchmarking constructor resolution. |
| benchmarks/EntityFrameworkCore.Projectables.Benchmarks/ExpressionResolverBenchmark.cs | Add registry/reflection benchmark cases for resolving the copy constructor expression. |
Comments suppressed due to low confidence (1)
src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs:311
expressionFactoryType.MakeGenericType(originalDeclaringType.GenericTypeArguments)will throw whenoriginalDeclaringTypeis a generic type definition (becauseGenericTypeArgumentsis empty). SinceFindGeneratedExpressionViaReflectionis public and documented for open-generic scenarios, consider handlingoriginalDeclaringType.IsGenericTypeDefinitionexplicitly (e.g., useGetGenericArguments()or short-circuit with a clear return/exception) before callingMakeGenericType.
if (expressionFactoryType.IsGenericTypeDefinition)
{
expressionFactoryType = expressionFactoryType.MakeGenericType(originalDeclaringType.GenericTypeArguments);
}
src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs
Outdated
Show resolved
Hide resolved
src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs
Outdated
Show resolved
Hide resolved
src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs
Outdated
Show resolved
Hide resolved
…sionResolver.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…sionResolver.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Likely yes, as the registry acts as a cold cache for many companion expressions and BuildReflectionExpression may still run many times concurrently upon startup if multiple concurrent requests hit it |
(Also add a benchmark for constructor resolution)
It is now as fast as the resgistry, so ... is the registry still needed ?