From ea6381e7c30c35f41be794a5cfe718d8de980211 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Thu, 2 Sep 2021 16:08:34 -0400 Subject: [PATCH] perf: Improve DependencyProperty propagation - Remove linq usage in GetChildrenDependencyObjects - Use HashTableEx in dependency property inheritance propagation to avoid JIT costs and improve lookup performance. --- ...pendencyObjectStore.AncestorsDictionary.cs | 54 +++++++++++++++++++ src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 20 ++++--- .../DependencyPropertyDetailsCollection.cs | 14 ++--- 3 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/DependencyObjectStore.AncestorsDictionary.cs diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.AncestorsDictionary.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.AncestorsDictionary.cs new file mode 100644 index 000000000000..460bae0c32e4 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.AncestorsDictionary.cs @@ -0,0 +1,54 @@ +#nullable enable + +using System; +using Uno.UI.DataBinding; +using System.Collections.Generic; +using Uno.Extensions; +using Uno.Logging; +using Uno.Diagnostics.Eventing; +using Uno.Disposables; +using System.Linq; +using System.Threading; +using Uno.Collections; +using System.Runtime.CompilerServices; +using System.Diagnostics; +using Windows.UI.Xaml.Data; +using Uno.UI; +using System.Collections; + +#if XAMARIN_ANDROID +using View = Android.Views.View; +#elif XAMARIN_IOS_UNIFIED +using View = UIKit.UIView; +#elif XAMARIN_IOS +using View = MonoTouch.UIKit.UIView; +#endif + +namespace Windows.UI.Xaml +{ + public partial class DependencyObjectStore : IDisposable + { + private class AncestorsDictionary + { + private readonly HashtableEx _entries = new HashtableEx(); + + internal bool TryGetValue(object key, out bool isAncestor) + { + if (_entries.TryGetValue(key, out var value)) + { + isAncestor = (bool)value!; + return true; + } + + isAncestor = false; + return false; + } + + internal void Set(object key, bool isAncestor) + => _entries[key] = isAncestor; + + internal void Clear() + => _entries.Clear(); + } + } +} diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 4cc086db9149..dae3541aa138 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -1183,11 +1183,17 @@ private void UpdateChildResourceBindings(bool isThemeChangedUpdate) /// private IEnumerable GetChildrenDependencyObjects() { - var propertyValues = _properties.GetAllDetails() - .Except(_properties.DataContextPropertyDetails, _properties.TemplatedParentPropertyDetails) - .Select(d => GetValue(d)); - foreach (var propertyValue in propertyValues) + foreach (var propertyDetail in _properties.GetAllDetails()) { + if(propertyDetail == null + || propertyDetail == _properties.DataContextPropertyDetails + || propertyDetail == _properties.TemplatedParentPropertyDetails) + { + continue; + } + + var propertyValue = GetValue(propertyDetail); + if (propertyValue is IEnumerable dependencyObjectCollection && // Try to avoid enumerating collections that shouldn't be enumerated, since we may be encountering user-defined values. This may need to be refined to somehow only consider values coming from the framework itself. (propertyValue is ICollection || propertyValue is DependencyObjectCollectionBase) @@ -1326,7 +1332,7 @@ private void PropagateInheritedNonLocalProperties(DependencyObjectStore? childSt // properties. // Ancestors is a local cache to avoid walking up the tree multiple times. - var ancestors = new Dictionary(); + var ancestors = new AncestorsDictionary(); // This alias is used to avoid the resolution of the underlying WeakReference during the // call to IsAncestor. @@ -1374,7 +1380,7 @@ void Propagate(DependencyObjectStore store) } } - private static bool IsAncestor(DependencyObject? instance, Dictionary map, object ancestor) + private static bool IsAncestor(DependencyObject? instance, AncestorsDictionary map, object ancestor) { #if DEBUG var hashSet = new HashSet(Uno.ReferenceEqualityComparer.Default); @@ -1417,7 +1423,7 @@ private static bool IsAncestor(DependencyObject? instance, Dictionary partial class DependencyPropertyDetailsCollection : IDisposable { - private static readonly DependencyPropertyDetails[] Empty = new DependencyPropertyDetails[0]; + private static readonly DependencyPropertyDetails?[] Empty = new DependencyPropertyDetails?[0]; private readonly Type _ownerType; private readonly ManagedWeakReference _ownerReference; @@ -33,9 +33,9 @@ partial class DependencyPropertyDetailsCollection : IDisposable private DependencyPropertyDetails? _dataContextPropertyDetails; private DependencyPropertyDetails? _templatedParentPropertyDetails; - private readonly static ArrayPool _pool = ArrayPool.Create(500, 100); + private readonly static ArrayPool _pool = ArrayPool.Create(500, 100); - private DependencyPropertyDetails[]? _entries; + private DependencyPropertyDetails?[]? _entries; private int _entriesLength; private int _minId; private int _maxId; @@ -56,7 +56,7 @@ public DependencyPropertyDetailsCollection(Type ownerType, ManagedWeakReference _templatedParentProperty = templatedParentProperty; } - private DependencyPropertyDetails[] Entries + private DependencyPropertyDetails?[] Entries { get { @@ -154,7 +154,7 @@ public DependencyPropertyDetails FindPropertyDetails(DependencyProperty property if (forceCreate) { int newEntriesSize; - DependencyPropertyDetails[] newEntries; + DependencyPropertyDetails?[] newEntries; if (entryIndex < 0) { @@ -210,7 +210,7 @@ private bool TryResolveDefaultValueFromProviders(DependencyProperty property, ou return false; } - private void AssignEntries(DependencyPropertyDetails[] newEntries, int newSize) + private void AssignEntries(DependencyPropertyDetails?[] newEntries, int newSize) { ReturnEntriesToPool(); @@ -230,7 +230,7 @@ private void ReturnEntriesToPool() } } - internal IEnumerable GetAllDetails() => Entries.Trim(); + internal DependencyPropertyDetails?[] GetAllDetails() => Entries; /// /// Adds a default value provider.