-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
[WinUI] Allocate less when updating gestures #21450
[WinUI] Allocate less when updating gestures #21450
Conversation
Hey there @MartyIX! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
@@ -60,5 +59,25 @@ static class EnumerableExtensions | |||
} | |||
} | |||
} | |||
|
|||
public static bool HasAnyGesturesFor<T>(this IEnumerable<IGestureRecognizer>? gestures, Func<T, bool>? predicate = null) where T : GestureRecognizer |
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.
This is to avoid allocation on L53.
|
||
_container.CanDrag = gestures.GetGesturesFor<DragGestureRecognizer>() | ||
.FirstOrDefault()?.CanDrag ?? false; | ||
bool canDrag = gestures.GetGesturesFor<DragGestureRecognizer>().FirstOrDefault()?.CanDrag ?? false; |
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.
Store value in a local variable, so we save 1 interop call on L692 (i.e. if (_container.CanDrag)
vs. if (canDrag)
) and L698.
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.
Can this use your new HasAnyGesturesFor()
method? And check CanDrag
inside the predicate?
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 have come up with acadf48. Not really sure if that is what you had in mind.
@@ -714,7 +718,7 @@ void UpdatingGestureRecognizers() | |||
IList<TapGestureRecognizer>? childGestures = | |||
children?.GetChildGesturesFor<TapGestureRecognizer>().ToList(); | |||
|
|||
if (gestures.GetGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 1).Any() | |||
if (gestures.HasAnyGesturesFor<TapGestureRecognizer>(g => g.NumberOfTapsRequired == 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.
This should save 1 allocation.
bool hasPanGesture = gestures.GetGesturesFor<PanGestureRecognizer>().GetEnumerator().MoveNext(); | ||
bool hasSwipeGesture = gestures.HasAnyGesturesFor<SwipeGestureRecognizer>(); | ||
bool hasPinchGesture = gestures.HasAnyGesturesFor<PinchGestureRecognizer>(); | ||
bool hasPanGesture = gestures.HasAnyGesturesFor<PanGestureRecognizer>(); |
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.
3 allocations here.
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.
So one thing to consider is this would still iterate over the gestures list three times. How many are normally in this list? If it's usually like 1-5, that might be OK.
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 think the list contains only a handful of items as you said.
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
bool hasPanGesture = gestures.GetGesturesFor<PanGestureRecognizer>().GetEnumerator().MoveNext(); | ||
bool hasSwipeGesture = gestures.HasAnyGesturesFor<SwipeGestureRecognizer>(); | ||
bool hasPinchGesture = gestures.HasAnyGesturesFor<PinchGestureRecognizer>(); | ||
bool hasPanGesture = gestures.HasAnyGesturesFor<PanGestureRecognizer>(); |
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.
So one thing to consider is this would still iterate over the gestures list three times. How many are normally in this list? If it's usually like 1-5, that might be OK.
@@ -60,5 +59,25 @@ static class EnumerableExtensions | |||
} | |||
} | |||
} | |||
|
|||
public static bool HasAnyGesturesFor<T>(this IEnumerable<IGestureRecognizer>? gestures, Func<T, bool>? predicate = null) where T : GestureRecognizer |
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.
Can we make the new method(s) internal
?
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.
Addressed in edd66ef.
|
||
_container.CanDrag = gestures.GetGesturesFor<DragGestureRecognizer>() | ||
.FirstOrDefault()?.CanDrag ?? false; | ||
bool canDrag = gestures.GetGesturesFor<DragGestureRecognizer>().FirstOrDefault()?.CanDrag ?? false; |
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.
Can this use your new HasAnyGesturesFor()
method? And check CanDrag
inside the predicate?
return false; | ||
} | ||
|
||
predicate ??= x => true; |
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.
Could we just do a null check inside the foreach
?
if (item is T gesture && (predicate is null || predicate(gesture)))
{
return true;
}
This way we aren't creating a lambda like x => true
.
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.
Addressed in 39b7ec6.
Main: Maui.Controls.Sample.Sandbox.exe_20240326_202255.speedscope.json -> UNMANAGED_CODE_TIME: 1:26 (1 minute 26 seconds) PR: Maui.Controls.Sample.Sandbox.exe_20240326_202543.speedscope.json -> UNMANAGED_CODE_TIME: 54.21s On sample code: MainPage.xaml <ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.MainPage"
x:DataType="local:MainPage"
xmlns:local="clr-namespace:Maui.Controls.Sample">
<VerticalStackLayout x:Name="myLayout">
<Button Text="Add" Clicked="Button_Clicked"/>
<Label Text="Drag me">
<Label.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Label.GestureRecognizers>
</Label>
</VerticalStackLayout>
</ContentPage> MainPage.xaml.cs using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace Maui.Controls.Sample
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void DragGestureRecognizer_DragStarting(object? sender, DragStartingEventArgs e)
{
}
private void Button_Clicked(object sender, EventArgs e)
{
for (int i = 0; i < 10000; i++)
{
// Add the following XAML.
/*
<Rectangle Stroke="Red" Fill="DarkBlue" StrokeThickness="4" HeightRequest="200" WidthRequest="200">
<Rectangle.GestureRecognizers>
<DragGestureRecognizer DragStarting="DragGestureRecognizer_DragStarting" />
</Rectangle.GestureRecognizers>
</Rectangle>
*/
Microsoft.Maui.Controls.Shapes.Rectangle rectangle = new()
{
Fill = Colors.DarkBlue,
Stroke = Colors.Red,
StrokeThickness = 4,
WidthRequest = 200,
HeightRequest = 200,
};
DragGestureRecognizer recognizer = new();
recognizer.DragStarting += DragGestureRecognizer_DragStarting;
rectangle.GestureRecognizers.Add(recognizer);
myLayout.Add(rectangle);
}
}
}
} |
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
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.
The changes make sense to me. 👍
But I think someone most familiar with Windows gestures on the MAUI team should also review.
@mattleibow Could you please take a look? |
Description of Change
This PR adds a few very basic optimizations to improve performance of
GesturePlatformManager.UpdateDragAndDropGestureRecognizers
and gestures in general.Measurements
Using commit e984695, I did:
Speedscope files:
Maui.Controls.Sample.Sandbox.exe_20240326_114219.speedscope.MAIN.json
Maui.Controls.Sample.Sandbox.exe_20240326_114435.speedscope.PR.json
Results
main:

PR:

Unfortunately, the results are not stable. So the results are not meaningful. I think one would have to start the application and then create gestures in code to measure it better. Any other ideas?
Issues Fixed
Contributes to #21449
See also: #21377