-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Reduce allocations while loading deferred resources #15070
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
Reduce allocations while loading deferred resources #15070
Conversation
You can test this PR using the following package version. |
7b29542
to
2d38408
Compare
You can test this PR using the following package version. |
2d38408
to
ef26dda
Compare
You can test this PR using the following package version. |
@@ -6,4 +6,11 @@ public interface IAvaloniaXamlIlParentStackProvider | |||
{ | |||
IEnumerable<object> Parents { get; } | |||
} | |||
|
|||
public interface IAvaloniaXamlIlEagerParentStackProvider : IAvaloniaXamlIlParentStackProvider |
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.
TODO: we should really avoid XamlIL
in public API when possible. Not a job for this PR, as it would end up in breaking changes anyway.
* Reduce allocations in XamlIlRuntimeHelpers * Iterate XAML parents without allocations * Use IDeferredContent for XAML deferred content to reduce allocations * Use function pointer in DeferredTransformationFactory * Reuse parent resource nodes if possible for deferred content * Fix function pointer usage with SRE
You are only doing a type check for the current stack provider, but |
What does the pull request do?
This PR aims to remove most unnecessary allocations related to deferred content inside resources dictionaries.
While loading the Fluent theme, there's a bit less than 1450 deferred items created, and that's normal.
However, they come with several allocations, both very short lived ones, and some longer ones, stored.
This PR removes more than 15000 allocations while loading the FluentTheme, half of which were long lived.
Resuls to load the
FluentTheme
:Note that it's still the JIT that takes most of the time when starting up the application (use NativeAOT when you can!), but I'll still take those gains :)
As part of this PR,
StaticResourceExtension.ProvideValue()
was also improved (not that it was slow to begin with):How was the solution implemented (if it's not obvious)?
All numbers below are for loading the Fluent theme.
Buffer resource nodes
In
XamlIlRuntimeHelpers
, a reusable buffer is now used to store the parent resources nodes and get an array out of that, without using Linq. The array is stored instead of the list.Removes ≈4350 allocations:
List<IResourceNode>
(long lived)OfType<IResourceNode>
enumerator (short lived)IResourceNode[]
backing array fromList
having to grow (short lived)Extra enumerator allocations happening when the deferred resource will be built have also been removed.
Cache default parents
DefaultAvaloniaXamlIlParentStackProvider
now caches its parent as an array, since it's only theApplication.Current
instance, which is very unlikely to change between two calls.Removes ≈1450 allocations:
Parents
enumerator (short lived)List parents without allocating
A specialized version of
IAvaloniaXamlIlParentStackProvider
is introducedIAvaloniaXamlIlEagerParentStackProvider
: it can iterate the parents without any allocation. This interface is implemented by the runtime XAML context.Removes ≈3500 allocations:
ParentStackEnumerable.Enumerator
(short lived)As a bonus, using this interface makes
StaticResource.ProvideValue()
allocation-free.Avoid delegate allocations
XamlIlRuntimeHelpers.DeferredTransformationFactoryV3
now returns anIDeferredContent
instead of aFunc<IServiceProvider, object>
.The returned closure directly implements this interface, so we can get rid of the delegate.
ResourceDictionary
is now aware ofIDeferredContent
and can store it directly.The XAML compiler now passes a function pointer to
DeferredTransformationFactoryV3
to build its content instead of creating a delegate.Removes ≈4350 allocations:
Func<IServiceProvider, object>
(long lived)DeferredItem
inside the resource dictionary (long lived)Reuse parent nodes
DeferredTransformationFactoryV3
reuses the parent resource nodes if they're the same as the last time it was called. This method is often called with the same parents (e.g. all items in the same resource dictionary), so this change provides nice gains. Out of 1450 items in the fluent theme, there are only 20 different lists of parents.Removes ≈1430 allocations:
IResourceNode[]
(long lived)Set resource dictionary capacity
ResourceDictionary.EnsureCapacity()
is called.This is #15069 which is the only one I could really make separate (all other modifications are intertwined), but that I measured as part of this PR.
Removes ≈30 allocations, but larger ones:
Dictionary+Entry[]
(short lived)Notes
ResourceDictionary.AddDeferred(IDeferredContent)
andAdd(IDeferredContent)
are equivalent. I didn't bother with disallowing one or the other, but this might be worth mentioning.Dependencies
This PR is complete, but is marked as a draft because it contains merges from several other PR:
When these get merged, I will rebase this PR to have a clean diff.