Skip to content

Commit 3871851

Browse files
[layout] avoid IView[] creation in LayoutExtensions (#8250)
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!
1 parent c289a45 commit 3871851

File tree

2 files changed

+15
-50
lines changed

2 files changed

+15
-50
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,23 @@
11
using System;
2-
using System.Collections.Generic;
2+
using System.Linq;
33

44
namespace Microsoft.Maui.Handlers
55
{
66
internal static class LayoutExtensions
77
{
8-
record ViewAndIndex(IView View, int Index);
8+
public static IOrderedEnumerable<IView> OrderByZIndex(this ILayout layout) => layout.OrderBy(v => v.ZIndex);
99

10-
class ZIndexComparer : IComparer<ViewAndIndex>
11-
{
12-
public int Compare(ViewAndIndex? x, ViewAndIndex? y)
13-
{
14-
if (x == null || y == null)
15-
{
16-
return 0;
17-
}
18-
19-
var zIndexCompare = x.View.ZIndex.CompareTo(y.View.ZIndex);
20-
21-
if (zIndexCompare == 0)
22-
{
23-
return x.Index.CompareTo(y.Index);
24-
}
25-
26-
return zIndexCompare;
27-
}
28-
}
29-
30-
static ZIndexComparer s_comparer = new();
31-
32-
public static IView[] OrderByZIndex(this ILayout layout)
10+
public static int GetLayoutHandlerIndex(this ILayout layout, IView view)
3311
{
34-
var count = layout.Count;
35-
var indexedViews = new ViewAndIndex[count];
36-
37-
for (int n = 0; n < count; n++)
12+
switch (layout.Count)
3813
{
39-
indexedViews[n] = new ViewAndIndex(layout[n], n);
14+
case 0:
15+
return -1;
16+
case 1:
17+
return view == layout[0] ? 0 : -1;
18+
default:
19+
return layout.OrderByZIndex().IndexOf(view);
4020
}
41-
42-
Array.Sort(indexedViews, s_comparer);
43-
44-
var ordered = new IView[count];
45-
46-
for (int n = 0; n < count; n++)
47-
{
48-
ordered[n] = indexedViews[n].View;
49-
}
50-
51-
return ordered;
52-
}
53-
54-
public static int GetLayoutHandlerIndex(this ILayout layout, IView view)
55-
{
56-
return layout.OrderByZIndex().IndexOf(view);
5721
}
5822
}
5923
}

src/Core/tests/UnitTests/Layouts/ZIndexTests.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using Microsoft.Maui.Graphics;
45
using Microsoft.Maui.Handlers;
56
using Microsoft.Maui.Primitives;
@@ -209,7 +210,7 @@ public void ItemsOrderByZIndex()
209210
layout.Add(view0);
210211
layout.Add(view1);
211212

212-
var zordered = layout.OrderByZIndex();
213+
var zordered = layout.OrderByZIndex().ToArray();
213214
Assert.Equal(view1, zordered[0]);
214215
Assert.Equal(view0, zordered[1]);
215216
}
@@ -228,7 +229,7 @@ public void ZIndexUpdatePreservesAddOrderForEqualZIndexes()
228229
layout.Add(view2);
229230
layout.Add(view3);
230231

231-
var zordered = layout.OrderByZIndex();
232+
var zordered = layout.OrderByZIndex().ToArray();
232233
Assert.Equal(view0, zordered[0]);
233234
Assert.Equal(view1, zordered[1]);
234235
Assert.Equal(view2, zordered[2]);
@@ -237,7 +238,7 @@ public void ZIndexUpdatePreservesAddOrderForEqualZIndexes()
237238
// Fake an update
238239
view3.ZIndex.Returns(5);
239240

240-
zordered = layout.OrderByZIndex();
241+
zordered = layout.OrderByZIndex().ToArray();
241242
Assert.Equal(view0, zordered[0]);
242243
Assert.Equal(view1, zordered[1]);
243244
Assert.Equal(view2, zordered[2]);
@@ -267,7 +268,7 @@ public void ZIndexUpdatePreservesAddOrderLotsOfEqualZIndexes()
267268

268269
for (int i = 0; i < iterations; i++)
269270
{
270-
var zordered = layout.OrderByZIndex();
271+
var zordered = layout.OrderByZIndex().ToArray();
271272

272273
for (int n = 0; n < zordered.Length; n++)
273274
{

0 commit comments

Comments
 (0)