Browse Source

Avoid initializing properties if there is no observer. Optimize access.

Dariusz Komosinski 6 năm trước cách đây
mục cha
commit
addc1ddce2

+ 5 - 0
src/Avalonia.Base/AvaloniaProperty.cs

@@ -492,6 +492,11 @@ namespace Avalonia
             return Name;
         }
 
+        /// <summary>
+        /// True if <see cref="Initialized"/> has any observers.
+        /// </summary>
+        internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
+
         /// <summary>
         /// Notifies the <see cref="Initialized"/> observable.
         /// </summary>

+ 56 - 18
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -24,8 +24,8 @@ namespace Avalonia
             new Dictionary<Type, List<AvaloniaProperty>>();
         private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
             new Dictionary<Type, List<AvaloniaProperty>>();
-        private readonly Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>> _initializedCache =
-            new Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>>();
+        private readonly Dictionary<Type, List<PropertyInitializationData>> _initializedCache =
+            new Dictionary<Type, List<PropertyInitializationData>>();
 
         /// <summary>
         /// Gets the <see cref="AvaloniaPropertyRegistry"/> 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<AvaloniaProperty, object>();
+                var visited = new HashSet<AvaloniaProperty>();
 
-                foreach (var property in GetRegistered(type))
+                initializationData = new List<PropertyInitializationData>();
+
+                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;
             }
         }
     }

+ 15 - 0
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();
+        }
+    }
+}