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

[WinUI] Perf when creating non-trivial grids (and other visual controls) (take 2) #22108

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

MartyIX
Copy link
Collaborator

@MartyIX MartyIX commented Apr 28, 2024

Description of Change

Depends on #21959

Alternative to #21818 which attempts to implement @jonathanpeppers's idea:

The part that makes me want to rethink this:

  1. Every control has TranslateX/Y default to 0
  2. Do we actually need to pass 0 to the platform? for every control?

I think there needs to be a concept of a default value
Where if you set TranslateX/Y it would pass to the platform and not otherwise
But I don't know if that breaks some fundamental design
this is just my observation
It would also be nice to get rid of one layer of Dictionary<string, lookup if that were possible

The idea of this PR is to introduce IView.IsPlatformViewNew which is true when there is a new platform view (which is known to have default values by default). So the first assignment of all properties can take into account this property and skip costly interop operations when an element is being added to the UI tree for the first time.

Relevant lines of code:

Notes:

Performance

Main: 851 ms

image

PR: 559 ms

image

-> 34% faster.

Issues Fixed

Contributes to #21787

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Apr 28, 2024
@@ -91,6 +91,8 @@ public string AutomationId
}
}

public bool IsPlatformViewNew { get; set; }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The name is terrible. It should be internal if possible.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think there is a way to make this internal if it is a property on IElement.

Is there a way this could detect if Handler was null and became non-null for the first time? Then maybe we wouldn't need a new bool property?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is there a way this could detect if Handler was null and became non-null for the first time? Then maybe we wouldn't need a new bool property?

I don't think I can simulate it that way because I need to set IsPlatformViewNew to true and then back to false:

an alternative which I would consider the best (in an ideal world) would be to pass a flag to:

_mapper.UpdateProperties(this, VirtualView);

but unfortunately then I would need to propagate that flag here:

action?.Invoke(viewHandler, virtualView);

and that would mean changing the way mappers are defined:

protected readonly Dictionary<string, Action<IElementHandler, IElement>> _mapper = new(StringComparer.Ordinal);

which seems like a very invasive thing to do (with many and many changes required). That's why I went with the IView.IsPlatformViewNew flag to avoid making changes to mappers.

Copy link
Contributor

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.

@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@jsuarezruiz jsuarezruiz added platform/windows 🪟 area-perf Startup / Runtime performance labels Apr 29, 2024
Copy link
Contributor

@jsuarezruiz jsuarezruiz left a comment

Choose a reason for hiding this comment

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

Based on the changes, needs to update also the Stubs used in the testing projects. For example:

D:\a\_work\1\s\src\Core\tests\DeviceTests.Shared\Stubs\WindowStub.cs(6,28): error CS0535: 'WindowStub' does not implement interface member 'IElement.IsPlatformViewNew' [D:\a\_work\1\s\src\Core\tests\DeviceTests.Shared\Core.DeviceTests.Shared.csproj::TargetFramework=net8.0-android]
    16 Error(s)

@MartyIX
Copy link
Collaborator Author

MartyIX commented Apr 29, 2024

Based on the changes, needs to update also the Stubs used in the testing projects. For example:

I tried to fix it in b5d7287.

I believe that this PR requires a conceptual review because it's not clear (to me) whether the approach is OK or not.

@@ -91,6 +91,8 @@ public string AutomationId
}
}

public bool IsPlatformViewNew { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

I don't think there is a way to make this internal if it is a property on IElement.

Is there a way this could detect if Handler was null and became non-null for the first time? Then maybe we wouldn't need a new bool property?

AutomationProperties.SetAutomationId(platformView, view.AutomationId);
public static void UpdateAutomationId(this FrameworkElement platformView, IView view)
{
if (!view.IsPlatformViewNew || view.AutomationId is not null)
Copy link
Member

Choose a reason for hiding this comment

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

Is the default for AutomationId null or string.Empty? I don't know the answer, so just checking.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I saw null when testing it so I used null but I will re-check.

Comment on lines 21 to 22
if (rotationX % 360 == 0 && rotationY % 360 == 0 && rotation % 360 == 0 &&
translationX == 0 && translationY == 0 && scaleX == 1 && scaleY == 1)
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way this could do slightly better if someone set ScaleX/ScaleY, but not rotation or translation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Personally, I think it would be a good start to extract changes from this file to a standalone PR because setting

frameworkElement.RenderTransformOrigin = new global::Windows.Foundation.Point(anchorX, anchorY);
frameworkElement.RenderTransform = new ScaleTransform { ScaleX = scaleX, ScaleY = scaleY };

and then setting

frameworkElement.RenderTransform = null;

feels like RenderTransformOrigin does not be set at all in that situation and it also seems to be quite costly. I would need to re-measure it but I think this small change would have noticeable effect.

Is there a way this could do slightly better if someone set ScaleX/ScaleY, but not rotation or translation?

I'll check it.

@Eilon Eilon added the t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.) label May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-perf Startup / Runtime performance community ✨ Community Contribution platform/windows 🪟 t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants