Skip to content

Commit fd921d7

Browse files
authored
Partial fix to #35053 - Is EF9 slower than EF8? (#35128)
Don't use Expression.Invoke in ValueComparer.ObjectEqualsExpression. ValueComparer now contains the information on how to build an expression representing Equals(object, object), which uses Expression.Invoke. We found this to be a major performance problem in some scenarios (e.g. include collection navigation) where that expression is executed large number of times by the result coordinator, as it is part of the parent/outer/selfIdentifierValueComparers. We actually know the lambda expression that is invoked in advance, so it's much more efficient to just remap the arguments and inline the lambda body into the ObjectEqualsExpression result. Benchmark results: ef 8 | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |-------------------------- |------ |---------:|--------:|--------:|------:|----------:|----------:|----------:| | PredicateMultipleIncludes | False | 147.2 ms | 2.63 ms | 2.46 ms | 6.793 | 4000.0000 | 3000.0000 | 26.24 MB | | PredicateMultipleIncludes | True | 159.1 ms | 3.00 ms | 2.95 ms | 6.287 | 5500.0000 | 3000.0000 | 34.47 MB | ef 9 without this change | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |-------------------------- |------ |---------:|--------:|--------:|------:|-----------:|----------:|----------:| | PredicateMultipleIncludes | False | 322.6 ms | 0.97 ms | 0.86 ms | 3.099 | 13000.0000 | 6000.0000 | 79.48 MB | | PredicateMultipleIncludes | True | 344.9 ms | 6.79 ms | 6.67 ms | 2.899 | 14000.0000 | 7000.0000 | 87.72 MB | ef 9 with this change | Method | Async | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Allocated | |-------------------------- |------ |---------:|--------:|--------:|------:|-----------:|----------:|----------:| | PredicateMultipleIncludes | False | 242.8 ms | 2.39 ms | 2.12 ms | 4.119 | 8000.0000 | 5000.0000 | 51.69 MB | | PredicateMultipleIncludes | True | 263.4 ms | 2.21 ms | 2.06 ms | 3.797 | 10000.0000 | 9000.0000 | 59.93 MB | Benchmarks indicate that this change represents a sizable chunk of the perf regression introduced in EF9 by the AOT changes, but doesn't fully address it. Part of #35053
1 parent 7e1b3a6 commit fd921d7

File tree

1 file changed

+6
-4
lines changed

1 file changed

+6
-4
lines changed

src/EFCore/ChangeTracking/ValueComparer`.cs

+6-4
Original file line numberDiff line numberDiff line change
@@ -263,16 +263,18 @@ public override LambdaExpression ObjectEqualsExpression
263263
var left = Parameter(typeof(object), "left");
264264
var right = Parameter(typeof(object), "right");
265265

266+
var remappedEquals = ReplacingExpressionVisitor.Replace(
267+
EqualsExpression.Parameters.ToList(),
268+
[Convert(left, typeof(T)), Convert(right, typeof(T))],
269+
EqualsExpression.Body);
270+
266271
_objectEqualsExpression = Lambda<Func<object?, object?, bool>>(
267272
Condition(
268273
Equal(left, Constant(null)),
269274
Equal(right, Constant(null)),
270275
AndAlso(
271276
NotEqual(right, Constant(null)),
272-
Invoke(
273-
EqualsExpression,
274-
Convert(left, typeof(T)),
275-
Convert(right, typeof(T))))),
277+
remappedEquals)),
276278
left,
277279
right);
278280
}

0 commit comments

Comments
 (0)