-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Caching access to virtual properties #24775
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I admit I'm surprised - this simple change seems to get us a 1% increase in RPS... Seems like we're at a point where virtual dispatch starts to make a difference?
We should do the same in SplitQueryingEnumerable, and possibly also look at FromSqlQueryingEnumerable (for DbDataReader) and Cosmos.
src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs
Outdated
Show resolved
Hide resolved
@@ -172,17 +172,18 @@ public bool MoveNext() | |||
|
|||
if (hasNext) | |||
{ | |||
var resultContext = _resultCoordinator.ResultContext; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single ResultContext is read-only on SingleQueryResultCoordinator, we could cache it as a field on SingleQueryingEnumerable rather than doing it on each MoveNext, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just make our result coordinators sealed and non-virtual properties? They are public and overridable because of pub-ternal philosophy. It it is hurting perf then I don't think it is worth sticking to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, we can go that way too. Though we're accessing some other virtual properties in MoveNext which we should probably cache (DbDataReader, CancellationToken), so we'd go down the caching route anyway...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make members on QueryContext
and derived classes non-virtual too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AndriySvyryd yeah.. I pushed a commit which at least caches QueryContext.Context so it doesn't get recalculated all the time.
Pushed a commit eliminating some more virtual lookups in the QueryingEnumerables. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need of _resultContext. Make whole structure for co-ordinating results non-virtual or leave them I can take care of it in future.
_cancellationToken/_dbDataReader are fine.
src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Relational/Query/Internal/SplitQueryingEnumerable.cs
Outdated
Show resolved
Hide resolved
Thanks again for this @vonzshik!! |
Reading from virtual properties in a cycle/sequentially is very slow because the compiler can't cache the returned value from the property (because there might be side effects). This pr helps to alleviate this problem for some of the properties (the worst offender is
QueryContext.Context
, which is called every time a newMaterializationContext
is created, but changing this is not trivial). Below is the benchmark results of reading from fields and properties in a cycle of 10.benchmark code
On fortunes benchmark this change got me around 50 ms/minute.
cc @roji