-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
[layout] avoid IView[] creation in LayoutExtensions #8250
Conversation
class ZIndexComparer : IComparer<ViewAndIndex> | ||
{ | ||
public int Compare(ViewAndIndex? x, ViewAndIndex? y) |
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.
It appears that a regular OrderBy()
is the same as this logic. Let me know if I'm missing something, thanks!
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.
Yeah, I only wrote it the hard way because otherwise I'd have to argue with people about the Linq :)
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 found other places where performance matters, that used OrderBy()
:
This is used when MSBuild evaluates a project, for every type of item group in a build. It would be called when VS loads a project.
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.
Wondering if we can just drop the OrderByZIndexMethod.
return layout.OrderByZIndex().IndexOf(view); | ||
switch (layout.Count) | ||
{ | ||
case 0: |
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 bet this is a huge chunk of the performance gain.
Context: https://github.com/unoplatform/performance/tree/master/src/dopes/DopeTestMaui Reviewing `dotnet trace` output of the "dopes" test sample, 7% of CPU time is spent in `LayoutExtensions.OrderByZIndex()`: 3.42s (16%) microsoft.maui!Microsoft.Maui.Handlers.LayoutHandler.Add(Microsoft.Maui.IView) 1.52s (7.3%) microsoft.maui!Microsoft.Maui.Handlers.LayoutExtensions.GetLayoutHandlerIndex(Microsoft.Maui.ILayout,Microsoft.Maui.IVie... 1.50s (7.2%) microsoft.maui!Microsoft.Maui.Handlers.LayoutExtensions.OrderByZIndex(Microsoft.Maui.ILayout) Reviewing the code in `OrderByZIndex()`: * Makes a `record ViewAndIndex(IView View, int Index)` for each child * Makes a `ViewAndIndex[]` the size of the number of children * Call `Array.Sort()` * Makes a `IView[]` the size of the number of children * Iterating twice over the arrays in the process Then if you look at the `GetLayoutHandlerIndex()` method, it does all the same work to create an `IView[]`, get the index, then throws the array away. I removed the above code and used a System.Linq `OrderBy()` with faster paths for 0 or 1 children in `GetLayoutHandlerIndex()`. I also made `OrderByZIndex()` return `IOrderedEnumerable` for use by `foreach` loops. This avoids creating arrays in those cases. The `ZIndexTests` still pass for me, which proves out `OrderBy()`'s stable sort: https://docs.microsoft.com/dotnet/api/system.linq.enumerable.orderby After this change, the % time spent in these methods went down: 1.84s (13%) microsoft.maui!Microsoft.Maui.Handlers.LayoutHandler.Add(Microsoft.Maui.IView) 352.24ms (2.50%) microsoft.maui!Microsoft.Maui.Handlers.LayoutExtensions.GetLayoutHandlerIndex(Microsoft.Maui.ILayout,Microsoft.Maui.IVie... 181.27ms (1.30%) microsoft.maui!Microsoft.Maui.Handlers.LayoutExtensions.<>c.<EnumerateByZIndex>b__0_0(Microsoft.Maui.IView) 2.78ms (0.02%) microsoft.maui!Microsoft.Maui.Handlers.LayoutExtensions.EnumerateByZIndex(Microsoft.Maui.ILayout) This kind of goes against the guidance "avoid System.Linq". But `OrderBy()` generally seems to be fine? ~~ Results ~~ A `Release` build on a Pixel 5 device, I was getting: Before: 103.04 Dopes/s After: 230.87 Dopes/s I suspect that was a lot of `IView[]`'s before!
1378ae2
to
24263d4
Compare
{ | ||
indexedViews[n] = new ViewAndIndex(layout[n], n); | ||
case 0: | ||
return -1; |
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.
was it returning -1 before ?
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.
Yep.
Context: https://github.com/unoplatform/performance/tree/master/src/dopes/DopeTestMaui
Reviewing
dotnet trace
output of the "dopes" test sample, 7% of CPUtime is spent in
LayoutExtensions.OrderByZIndex()
:Reviewing the code in
OrderByZIndex()
:record ViewAndIndex(IView View, int Index)
for each child
ViewAndIndex[]
the size of the number of childrenArray.Sort()
IView[]
the size of the number of childrenThen if you look at the
GetLayoutHandlerIndex()
method, it does allthe same work to create an
IView[]
, get the index, then throws thearray away.
I removed the above code and used a System.Linq
OrderBy()
withfaster paths for 0 or 1 children. I also made an
internal
EnumerateByZIndex()
method for use byforeach
loops. This avoidscreating arrays in those cases. The
ZIndexTests
still pass for me,which proves out
OrderBy()
's stable sort:https://docs.microsoft.com/dotnet/api/system.linq.enumerable.orderby
After this change, the % time spent in these methods went down:
This kind of goes against the guidance "avoid System.Linq".
But
OrderBy()
generally seems to be fine?Results
A
Release
build on a Pixel 5 device, I was getting:I suspect that was a lot of
IView[]
's before!