-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[android] avoid OnLayout() for Label
#21291
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
jonathanpeppers
merged 1 commit into
dotnet:main
from
jonathanpeppers:PlatformAppCompatTextView
Mar 27, 2024
Merged
[android] avoid OnLayout() for Label
#21291
jonathanpeppers
merged 1 commit into
dotnet:main
from
jonathanpeppers:PlatformAppCompatTextView
Mar 27, 2024
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
f5d4177 to
e6216a5
Compare
jonathanpeppers
added a commit
to jonathanpeppers/maui
that referenced
this pull request
Mar 20, 2024
Applies to: dotnet#18505 Context: https://github.com/dotnet/maui/files/13251041/MauiCollectionView.zip I profiled the above sample with `dotnet-trace` with the following PRs applied locally: * dotnet#21229 * dotnet#21291 While scrolling, a lot of time is spent in `ResourceDictionary` lookups on an Android Pixel 5 device: 2.0% Microsoft.Maui.Controls!Microsoft.Maui.Controls.ResourceDictionary.TryGetValue(string,object&) Drilling in, I can see System.Linq's `Reverse()` method: 0.56% System.Linq!System.Linq.Enumerable.ReverseIterator<TSource_REF>.MoveNext() 0.14% System.Linq!System.Linq.Enumerable.Reverse(System.Collections.Generic.IEnumerable`1<TSource_REF>) 0.04% System.Linq!System.Linq.Enumerable.ReverseIterator<TSource_REF>..ctor(System.Collections.Generic.IEnumerable`1<TSource_REF>) 0.04% System.Linq!System.Linq.Enumerable.ReverseIterator<TSource_REF>.Dispose() `Reverse()` can be problematic as it can sometimes create a copy of the entire collection, in order to sort in reverse. We can juse use a reverse `for`-loop instead. The indexer, we can also avoid a double-lookup: if (dict.ContainsKey(index)) return dict[index]; And instead do: if (dict.TryGetValue(index, out var value)) return value; The MAUI project template seems to setup a few "merged" `ResourceDictionary` as it contains `Styles.xaml`, so this is why this code path is being hit. I wrote a BenchmarkDotNet benchmark, and it indicates the collection is being copied, as the 872 bytes of allocation occur: | Method | key | Mean | Error | StdDev | Gen0 | Allocated | |------------ |------------ |----------:|---------:|---------:|-------:|----------:| | TryGetValue | key0 | 11.45 ns | 0.026 ns | 0.023 ns | - | - | | Indexer | key0 | 24.72 ns | 0.133 ns | 0.118 ns | - | - | | TryGetValue | merged99,99 | 117.06 ns | 2.334 ns | 2.497 ns | 0.1042 | 872 B | | Indexer | merged99,99 | 145.60 ns | 2.737 ns | 2.286 ns | 0.1042 | 872 B | With these changes in place, I see less time spent inside: 0.91% Microsoft.Maui.Controls!Microsoft.Maui.Controls.ResourceDictionary.TryGetValue(string,object&) The benchmark no longer allocates either: | Method | key | Mean | Error | StdDev | Allocated | |------------ |------------ |----------:|----------:|----------:|----------:| | TryGetValue | key0 | 11.92 ns | 0.094 ns | 0.084 ns | - | | Indexer | merged99,99 | 23.12 ns | 0.418 ns | 0.391 ns | - | | Indexer | key0 | 24.20 ns | 0.485 ns | 0.453 ns | - | | TryGetValue | merged99,99 | 29.09 ns | 0.296 ns | 0.262 ns | - | This should improve the performance "parenting" of any MAUI view on all platforms -- as well as scrolling `CollectionView`.
Context: dotnet#18505 Context: https://github.com/dotnet/maui/files/13251041/MauiCollectionView.zip Context: dotnet#21229 (review) In profiling scrolling of an app with a `<CollectionView/>` and 12 `<Label/>`s, we see time spent in: 1.9% Microsoft.Maui!Microsoft.Maui.Platform.MauiTextView.OnLayout(bool,int,int,int,int) This is a callback from Java to C#, which has a performance cost. Reviewing the code, we would only need to make this callback *at all* if `Label.FormattedText` is not `null`. The bulk of all `Label`'s can avoid this call? To do this: * Write a new `PlatformAppCompatTextView.java` that override `onLayout()` * It only calls `onLayoutFormatted()` if a `isFormatted` `boolean` field is `true` * We can set `isFormatted` if a formatted string is such as: `isFormatted = !(text instanceof String)` With this change in place, the above `MauiTextView.OnLayout()` call is completely gone from `dotnet-trace` output. Scrolling the sample also "feels" a bit snappier. This should improve the performance of all non-formatted `Label`s on Android. This is the mininum amount of API changes possible -- which seems like what we should go for if we ship this change in .NET 8 servicing.
60a21d9 to
c8aebdc
Compare
jonathanpeppers
commented
Mar 26, 2024
Comment on lines
+20
to
+24
| @Override | ||
| public void setText(CharSequence text, BufferType type) { | ||
| isFormatted = !(text instanceof String); | ||
| super.setText(text, type); | ||
| } |
Member
Author
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.
Note that calling TextView.Text = "foo" will call inside here:
The C# TextView.Text property is bound to setText(CharSequence).
jsuarezruiz
approved these changes
Mar 27, 2024
StephaneDelcroix
pushed a commit
that referenced
this pull request
Mar 27, 2024
Applies to: #18505 Context: https://github.com/dotnet/maui/files/13251041/MauiCollectionView.zip I profiled the above sample with `dotnet-trace` with the following PRs applied locally: * #21229 * #21291 While scrolling, a lot of time is spent in `ResourceDictionary` lookups on an Android Pixel 5 device: 2.0% Microsoft.Maui.Controls!Microsoft.Maui.Controls.ResourceDictionary.TryGetValue(string,object&) Drilling in, I can see System.Linq's `Reverse()` method: 0.56% System.Linq!System.Linq.Enumerable.ReverseIterator<TSource_REF>.MoveNext() 0.14% System.Linq!System.Linq.Enumerable.Reverse(System.Collections.Generic.IEnumerable`1<TSource_REF>) 0.04% System.Linq!System.Linq.Enumerable.ReverseIterator<TSource_REF>..ctor(System.Collections.Generic.IEnumerable`1<TSource_REF>) 0.04% System.Linq!System.Linq.Enumerable.ReverseIterator<TSource_REF>.Dispose() `Reverse()` can be problematic as it can sometimes create a copy of the entire collection, in order to sort in reverse. We can juse use a reverse `for`-loop instead. The indexer, we can also avoid a double-lookup: if (dict.ContainsKey(index)) return dict[index]; And instead do: if (dict.TryGetValue(index, out var value)) return value; The MAUI project template seems to setup a few "merged" `ResourceDictionary` as it contains `Styles.xaml`, so this is why this code path is being hit. I wrote a BenchmarkDotNet benchmark, and it indicates the collection is being copied, as the 872 bytes of allocation occur: | Method | key | Mean | Error | StdDev | Gen0 | Allocated | |------------ |------------ |----------:|---------:|---------:|-------:|----------:| | TryGetValue | key0 | 11.45 ns | 0.026 ns | 0.023 ns | - | - | | Indexer | key0 | 24.72 ns | 0.133 ns | 0.118 ns | - | - | | TryGetValue | merged99,99 | 117.06 ns | 2.334 ns | 2.497 ns | 0.1042 | 872 B | | Indexer | merged99,99 | 145.60 ns | 2.737 ns | 2.286 ns | 0.1042 | 872 B | With these changes in place, I see less time spent inside: 0.91% Microsoft.Maui.Controls!Microsoft.Maui.Controls.ResourceDictionary.TryGetValue(string,object&) The benchmark no longer allocates either: | Method | key | Mean | Error | StdDev | Allocated | |------------ |------------ |----------:|----------:|----------:|----------:| | TryGetValue | key0 | 11.92 ns | 0.094 ns | 0.084 ns | - | | Indexer | merged99,99 | 23.12 ns | 0.418 ns | 0.391 ns | - | | Indexer | key0 | 24.20 ns | 0.485 ns | 0.453 ns | - | | TryGetValue | merged99,99 | 29.09 ns | 0.296 ns | 0.262 ns | - | This should improve the performance "parenting" of any MAUI view on all platforms -- as well as scrolling `CollectionView`.
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
area-controls-label
Label, Span
fixed-in-8.0.20
fixed-in-9.0.0-preview.3.10457
perf/general
The issue affects performance (runtime speed, memory usage, startup time, etc.) (sub: perf)
platform/android
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Context: #18505
Context: https://github.com/dotnet/maui/files/13251041/MauiCollectionView.zip
Context: #21229 (review)
In profiling scrolling of an app with a
<CollectionView/>and 12<Label/>s, we see time spent in:This is a callback from Java to C#, which has a performance cost.
Reviewing the code, we would only need to make this callback at all
if
Label.FormattedTextis notnull. The bulk of allLabel's canavoid this call?
To do this:
Write a new
PlatformAppCompatTextView.javathat overrideonLayout()It only calls
onLayoutFormatted()if aisFormattedbooleanfield is
trueWe can set
isFormattedif a formatted string is passed in, such as:isFormatted = !(text instanceof String)With this change in place, the above
MauiTextView.OnLayout()call iscompletely gone from
dotnet-traceoutput. Scrolling the sample also"feels" a bit snappier.
This should improve the performance of all non-formatted
Labels onAndroid.
This is the mininum amount of API changes possible -- which seems like
what we should go for if we ship this change in .NET 8 servicing.