Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial fix to #35053 - Is EF9 slower than EF8? #35128

Merged
merged 1 commit into from
Nov 18, 2024
Merged

Conversation

maumar
Copy link
Contributor

@maumar maumar commented Nov 17, 2024

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

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
@@ -263,16 +263,18 @@ public override LambdaExpression ObjectEqualsExpression
var left = Parameter(typeof(object), "left");
var right = Parameter(typeof(object), "right");

var remappedEquals = ReplacingExpressionVisitor.Replace(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, although I wonder if we are sometimes doing work (casts and comparisons) here that we don't need to do in some cases. I will add a note to #29909, which is about perf improvements in this area.

Copy link
Contributor

@ajcvickers ajcvickers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving this, but I think it would be good to discuss what we can learn from this regression and what we might be able to do to catch this sooner in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants