Skip to content

Improve PropertyMapper performance #28077

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

Merged

Conversation

albyrock87
Copy link
Contributor

@albyrock87 albyrock87 commented Feb 27, 2025

Description of Change

  • Improve PropertyMapper performance by caching mappers (same thing is currently done on mapper's keys)
    • Moves viewHandler.CanInvokeMappers() check to UpdateProperties level considering that on Android this method method uses the Java object
  • Ensures properties are executed in the order they're defined (this will help with future perf developments Skip useless handler mappings calls while connecting #27259)
    • Brings to light race conditions now present on a few mappers (like MapToolbar depends on MapFlyout) and formally solves them by setting the right order of execution

Benchmarks

BenchmarkDotNet v0.13.10, macOS 15.3.1 (24D70) [Darwin 24.3.0]
Apple M3 Pro, 1 CPU, 11 logical and 11 physical cores
.NET SDK 9.0.102

Before

Method Mean Error StdDev Gen0 Allocated
BenchmarkUpdateProperties 167.67 ms 0.584 ms 0.547 ms 3333.3333 29604261 B
BenchmarkUpdateProperty 31.65 ms 0.079 ms 0.070 ms - 46 B

After

Method Mean Error StdDev Gen0 Allocated
BenchmarkUpdateProperties 59.04 ms 0.294 ms 0.275 ms 3666.6667 31204122 B
BenchmarkUpdateProperty 12.39 ms 0.026 ms 0.020 ms - 12 B

@albyrock87 albyrock87 requested a review from a team as a code owner February 27, 2025 12:58
@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Feb 27, 2025
@albyrock87 albyrock87 changed the title Improve property mapper performance Improve PropertyMapper performance Feb 27, 2025
// when we call UpdateProperties
HashSet<string>? _updateKeys;
IReadOnlyDictionary<string, Action<IElementHandler, IElement>>? _mergedMappers;
IReadOnlyDictionary<string, Action<IElementHandler, IElement>> MergedMappers => _mergedMappers ?? SnapshotMappers().Mappers;
Copy link
Contributor

@MartyIX MartyIX Feb 27, 2025

Choose a reason for hiding this comment

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

There is OrderedDictionary in .NET 9 and I wonder if it is useful here or somewhere else.

(I wanted to take a look at "ordering of mapper calls" for a very long time. Mostly to make sure that a wrapper view is created by the very first mapper call. I'm very happy that you beat me in working on this. :))

Copy link
Contributor Author

@albyrock87 albyrock87 Feb 27, 2025

Choose a reason for hiding this comment

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

That one is not generic, so it comes with the cost of casting everything, and I also think it has a cost in performance compared to the generic implementation.

That said, we'd have to change _mapper too in that case (which is a public API unfortunately), or add an additional List<string> _keys property to use while inserting new mappers.

I think we can safely bet on this given it's covered by unit tests, if it changes we can switch to something else.

Copy link
Contributor

@MartyIX MartyIX Feb 27, 2025

Choose a reason for hiding this comment

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

I modified the link. It's generic.

Anyway, it's fine by me. I just wanted to mention that the ordered variant exists now. If it used or not, it's not as relevant as if all design choices were considered.

@albyrock87 albyrock87 force-pushed the improve-property-mapper-performance branch from f0c1756 to 7b1bc2b Compare February 27, 2025 14:00
@jsuarezruiz jsuarezruiz added the t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.) (sub: perf) label Feb 27, 2025
Copy link
Member

@mattleibow mattleibow left a comment

Choose a reason for hiding this comment

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

It is late, and I am reviewing with my brain turned off. But I got some questions that maybe I wouldn't have asked in the morning. But the PR looks nice.

@albyrock87 albyrock87 force-pushed the improve-property-mapper-performance branch from 7b1bc2b to f255038 Compare February 28, 2025 13:13
@albyrock87
Copy link
Contributor Author

@mattleibow I've updated the PR, including the fix for the broken tests: I was totally missing the special AndroidBatchPropertyMapper.

Also I backported the race-condition fix on iOS ContainerViewController I've done in the other PR.

{
foreach (var key in _mapper.Keys)
// When doing the initial properties batch updates, ignore properties in the skip list.
// These will be handled by ViewHandler.SetVirtualView() instead.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@albyrock87 albyrock87 force-pushed the improve-property-mapper-performance branch from f255038 to bf03d95 Compare March 1, 2025 10:35
@albyrock87
Copy link
Contributor Author

albyrock87 commented Mar 1, 2025

Note: these failing tests are unrelated:

  • mac: UpdatedIsEnabledProperty.png (size differs - baseline is 1920x1080 pixels, actual is 789x563 pixels)
  • mac: FlyoutItemTextShouldDisplayProperly.png (size differs - baseline is 1920x1080 pixels, actual is 789x563 pixels)
  • mac: RemainingItemsThresholdReachedCommandFired => passed locally

See more at: #28125

@albyrock87 albyrock87 force-pushed the improve-property-mapper-performance branch 2 times, most recently from d06c4c8 to 4395e1f Compare March 1, 2025 10:42
@Redth
Copy link
Member

Redth commented Mar 1, 2025

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@PureWeen PureWeen added this to the .NET 9 SR5 milestone Mar 1, 2025
@albyrock87 albyrock87 force-pushed the improve-property-mapper-performance branch from 4395e1f to 7762a08 Compare March 2, 2025 10:05
@PureWeen
Copy link
Member

PureWeen commented Mar 2, 2025

/azp run

@PureWeen PureWeen added the p/0 Work that we can't release without label Mar 2, 2025
@albyrock87 albyrock87 force-pushed the improve-property-mapper-performance branch 4 times, most recently from 03f142c to b42a518 Compare March 3, 2025 10:59
@albyrock87
Copy link
Contributor Author

albyrock87 commented Mar 3, 2025

@mattleibow so I had to refactor the code again due to the AndroidBatchedPropertyMapper.

The introduction of that class changed the meaning of GetKeys

returns the list of defined mappers

to something like:

returns the list of keys to be updated in UpdateProperties

This is really unfortunate because now there's no way to tell what are the defined keys.

Initially I thought of simply moving the skip list into PropertyMapper and restore the original meaning of GetKeys, but turns out that I can't because a parent mapper could re-define a given mapper and therefore requiring it's execution.
For example SwipeViewHandler redefines IsEnabled which is part of the android skip list.

On top of that, if the user ever wanted to define their own IsEnabled at ViewHandler level (or even AppendToMapping), their mapper would never be executed due to the skip list, which is obviously wrong.

I've added unit tests to cover these scenarios.

#27259 will enable us to detect when we're running the initial mapping, so we will be able to remove AndroidBatchedPropertyMapper and just turn every skip-list mapper into a noop during the initial mapping, for example (see more here):

public static void MapVisibility(IViewHandler handler, IView view)
{
#if ANDROID
    if (handler.IsConnectingHandler()) return;
#else
    if (handler.IsConnectingHandler() && view.Visibility == Visibility.Visible) return;
#endif

    if (handler.HasContainer)
        ((PlatformView?)handler.ContainerView)?.UpdateVisibility(view);

This way the user can even define their own mappers ensuring they will be executed on the first mapping.

@albyrock87 albyrock87 force-pushed the improve-property-mapper-performance branch from b42a518 to 6ee08fc Compare March 3, 2025 11:58
@PureWeen
Copy link
Member

PureWeen commented Mar 3, 2025

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@github-actions github-actions bot force-pushed the improve-property-mapper-performance branch from 6ee08fc to bd51dc1 Compare March 4, 2025 13:07
@PureWeen
Copy link
Member

PureWeen commented Mar 4, 2025

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@albyrock87
Copy link
Contributor Author

Failing tests look unrelated:

  • RegisterTwoDoubleTapHandlersAndCheckNumberOfFiredEventsAsync => passes locally
  • CarouselViewShouldRenderCorrectly => it's randomly mismatching by 1px (not only in this PR)
  • BindingUpdatesFromInteractiveRefresh => I have no clue, and no Windows machine to verify
  • CarouselItemsShouldRenderProperly (windows) => the screenshot differs only due to the horizontal scrollbar

@dotnet dotnet deleted a comment from azure-pipelines bot Mar 6, 2025
@dotnet dotnet deleted a comment from jfversluis Mar 6, 2025
@PureWeen
Copy link
Member

PureWeen commented Mar 6, 2025

/rebase

@github-actions github-actions bot force-pushed the improve-property-mapper-performance branch from bd51dc1 to 475b7b3 Compare March 6, 2025 16:23
@PureWeen
Copy link
Member

PureWeen commented Mar 6, 2025

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@github-project-automation github-project-automation bot moved this from Ready To Review to Approved in MAUI SDK Ongoing Mar 6, 2025
@PureWeen
Copy link
Member

PureWeen commented Mar 7, 2025

  • failing tests are unrelated

@PureWeen PureWeen merged commit 2c74335 into dotnet:main Mar 7, 2025
121 of 128 checks passed
@github-project-automation github-project-automation bot moved this from Approved to Done in MAUI SDK Ongoing Mar 7, 2025
rmarinho pushed a commit that referenced this pull request Mar 11, 2025
@github-actions github-actions bot locked and limited conversation to collaborators Apr 7, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
community ✨ Community Contribution p/0 Work that we can't release without t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.) (sub: perf)
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

7 participants