From addc1ddce2e4a2200953277517f54b92f941b3d0 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 30 Aug 2019 01:13:19 +0200 Subject: [PATCH] Avoid initializing properties if there is no observer. Optimize access. --- src/Avalonia.Base/AvaloniaProperty.cs | 5 ++ src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 74 ++++++++++++++----- .../AvaloniaObjectInitializationBenchmark.cs | 15 ++++ 3 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 1de5cb06c6..56ad241187 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -492,6 +492,11 @@ namespace Avalonia return Name; } + /// + /// True if has any observers. + /// + internal bool HasNotifyInitializedObservers => _initialized.HasObservers; + /// /// Notifies the observable. /// diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 037e0dd72e..88b0201fcb 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -24,8 +24,8 @@ namespace Avalonia new Dictionary>(); private readonly Dictionary> _attachedCache = new Dictionary>(); - private readonly Dictionary>> _initializedCache = - new Dictionary>>(); + private readonly Dictionary> _initializedCache = + new Dictionary>(); /// /// Gets the instance @@ -286,35 +286,73 @@ namespace Avalonia property.NotifyInitialized(e); } - if (!_initializedCache.TryGetValue(type, out var items)) + if (!_initializedCache.TryGetValue(type, out var initializationData)) { - var build = new Dictionary(); + var visited = new HashSet(); - foreach (var property in GetRegistered(type)) + initializationData = new List(); + + foreach (AvaloniaProperty property in GetRegistered(type)) { - var value = !property.IsDirect ? - ((IStyledPropertyAccessor)property).GetDefaultValue(type) : - null; - build.Add(property, value); + if (property.IsDirect) + { + initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property)); + } + else + { + initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type)); + } + + visited.Add(property); } - foreach (var property in GetRegisteredAttached(type)) + foreach (AvaloniaProperty property in GetRegisteredAttached(type)) { - if (!build.ContainsKey(property)) + if (!visited.Contains(property)) { - var value = ((IStyledPropertyAccessor)property).GetDefaultValue(type); - build.Add(property, value); + initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type)); + + visited.Add(property); } } - items = build.ToList(); - _initializedCache.Add(type, items); + _initializedCache.Add(type, initializationData); + } + + foreach (PropertyInitializationData data in initializationData) + { + if (!data.Property.HasNotifyInitializedObservers) + { + continue; + } + + object value = data.IsDirect ? data.DirectAccessor.GetValue(o) : data.Value; + + Notify(data.Property, value); + } + } + + private readonly struct PropertyInitializationData + { + public AvaloniaProperty Property { get; } + public object Value { get; } + public bool IsDirect { get; } + public IDirectPropertyAccessor DirectAccessor { get; } + + public PropertyInitializationData(AvaloniaProperty property, IDirectPropertyAccessor directAccessor) + { + Property = property; + Value = null; + IsDirect = true; + DirectAccessor = directAccessor; } - foreach (var i in items) + public PropertyInitializationData(AvaloniaProperty property, IStyledPropertyAccessor styledAccessor, Type type) { - var value = i.Key.IsDirect ? o.GetValue(i.Key) : i.Value; - Notify(i.Key, value); + Property = property; + Value = styledAccessor.GetDefaultValue(type); + IsDirect = false; + DirectAccessor = null; } } } diff --git a/tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs b/tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs new file mode 100644 index 0000000000..06716f7102 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs @@ -0,0 +1,15 @@ +using Avalonia.Controls; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Base +{ + [MemoryDiagnoser] + public class AvaloniaObjectInitializationBenchmark + { + [Benchmark(OperationsPerInvoke = 1000)] + public Button InitializeButton() + { + return new Button(); + } + } +}