Browse Source

Improved performance of value lookup in AvaloniaObject's ValueStore

mstr2 7 years ago
parent
commit
c5e4996da2
2 changed files with 156 additions and 16 deletions
  1. 15 1
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  2. 141 15
      src/Avalonia.Base/ValueStore.cs

+ 15 - 1
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -13,6 +13,8 @@ namespace Avalonia
     /// </summary>
     public class AvaloniaPropertyRegistry
     {
+        private readonly Dictionary<int, AvaloniaProperty> _allProperties =
+            new Dictionary<int, AvaloniaProperty>();
         private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
             new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
         private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
@@ -148,6 +150,16 @@ namespace Avalonia
             return FindRegistered(o.GetType(), name);
         }
 
+        /// <summary>
+        /// Finds a registered property by Id.
+        /// </summary>
+        /// <param name="id">The property Id.</param>
+        /// <returns>The registered property or null if no matching property found.</returns>
+        public AvaloniaProperty FindRegistered(int id)
+        {
+            return _allProperties.TryGetValue(id, out var value) ? value : null;
+        }
+
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is registered on a type.
         /// </summary>
@@ -202,7 +214,8 @@ namespace Avalonia
             {
                 inner.Add(property.Id, property);
             }
- 
+
+            _allProperties[property.Id] = property;
             _registeredCache.Clear();
         }
 
@@ -238,6 +251,7 @@ namespace Avalonia
                 inner.Add(property.Id, property);
             }
 
+            _allProperties[property.Id] = property;
             _attachedCache.Clear();
         }
     }

+ 141 - 15
src/Avalonia.Base/ValueStore.cs

@@ -7,13 +7,21 @@ namespace Avalonia
 {
     internal class ValueStore : IPriorityValueOwner
     {
+        struct Entry
+        {
+            internal int PropertyId;
+            internal object Value;
+        }
+
         private readonly AvaloniaObject _owner;
-        private readonly Dictionary<AvaloniaProperty, object> _values =
-            new Dictionary<AvaloniaProperty, object>();
+        private Entry[] _entries;
 
         public ValueStore(AvaloniaObject owner)
         {
             _owner = owner;
+
+            // The last item in the list is always int.MaxValue
+            _entries = new[] { new Entry { PropertyId = int.MaxValue, Value = null } };
         }
 
         public IDisposable AddBinding(
@@ -23,7 +31,7 @@ namespace Avalonia
         {
             PriorityValue priorityValue;
 
-            if (_values.TryGetValue(property, out var v))
+            if (TryGetValue(property, out var v))
             {
                 priorityValue = v as PriorityValue;
 
@@ -31,13 +39,13 @@ namespace Avalonia
                 {
                     priorityValue = CreatePriorityValue(property);
                     priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
-                    _values[property] = priorityValue;
+                    SetValueInternal(property, priorityValue);
                 }
             }
             else
             {
                 priorityValue = CreatePriorityValue(property);
-                _values.Add(property, priorityValue);
+                AddValueInternal(property, priorityValue);
             }
 
             return priorityValue.Add(source, (int)priority);
@@ -47,7 +55,7 @@ namespace Avalonia
         {
             PriorityValue priorityValue;
 
-            if (_values.TryGetValue(property, out var v))
+            if (TryGetValue(property, out var v))
             {
                 priorityValue = v as PriorityValue;
 
@@ -55,7 +63,7 @@ namespace Avalonia
                 {
                     if (priority == (int)BindingPriority.LocalValue)
                     {
-                        _values[property] = Validate(property, value);
+                        SetValueInternal(property, Validate(property, value));
                         Changed(property, priority, v, value);
                         return;
                     }
@@ -63,7 +71,7 @@ namespace Avalonia
                     {
                         priorityValue = CreatePriorityValue(property);
                         priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
-                        _values[property] = priorityValue;
+                        SetValueInternal(property, priorityValue);
                     }
                 }
             }
@@ -76,14 +84,14 @@ namespace Avalonia
 
                 if (priority == (int)BindingPriority.LocalValue)
                 {
-                    _values.Add(property, Validate(property, value));
+                    AddValueInternal(property, Validate(property, value));
                     Changed(property, priority, AvaloniaProperty.UnsetValue, value);
                     return;
                 }
                 else
                 {
                     priorityValue = CreatePriorityValue(property);
-                    _values.Add(property, priorityValue);
+                    AddValueInternal(property, priorityValue);
                 }
             }
 
@@ -100,13 +108,22 @@ namespace Avalonia
             _owner.PriorityValueChanged(property, priority, oldValue, newValue);
         }
 
-        public IDictionary<AvaloniaProperty, object> GetSetValues() => _values;
+        public IDictionary<AvaloniaProperty, object> GetSetValues()
+        {
+            var dict = new Dictionary<AvaloniaProperty, object>(_entries.Length - 1);
+            for (int i = 0; i < _entries.Length - 1; ++i)
+            {
+                dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value);
+            }
+
+            return dict;
+        }
 
         public object GetValue(AvaloniaProperty property)
         {
             var result = AvaloniaProperty.UnsetValue;
 
-            if (_values.TryGetValue(property, out var value))
+            if (TryGetValue(property, out var value))
             {
                 result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
             }
@@ -116,12 +133,12 @@ namespace Avalonia
 
         public bool IsAnimating(AvaloniaProperty property)
         {
-            return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
+            return TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
         }
 
         public bool IsSet(AvaloniaProperty property)
         {
-            if (_values.TryGetValue(property, out var value))
+            if (TryGetValue(property, out var value))
             {
                 return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
             }
@@ -131,7 +148,7 @@ namespace Avalonia
 
         public void Revalidate(AvaloniaProperty property)
         {
-            if (_values.TryGetValue(property, out var value))
+            if (TryGetValue(property, out var value))
             {
                 (value as PriorityValue)?.Revalidate();
             }
@@ -178,5 +195,114 @@ namespace Avalonia
                     (_deferredSetter = new DeferredSetter<object>());
             }
         }
+
+        private bool TryGetValue(AvaloniaProperty property, out object value)
+        {
+            (int index, bool found) = TryFindEntry(property.Id);
+            if (!found)
+            {
+                value = null;
+                return false;
+            }
+
+            value = _entries[index].Value;
+            return true;
+        }
+
+        private void AddValueInternal(AvaloniaProperty property, object value)
+        {
+            Entry[] entries = new Entry[_entries.Length + 1];
+
+            for (int i = 0; i < _entries.Length; ++i)
+            {
+                if (_entries[i].PropertyId > property.Id)
+                {
+                    if (i > 0)
+                    {
+                        Array.Copy(_entries, 0, entries, 0, i);
+                    }
+
+                    entries[i] = new Entry { PropertyId = property.Id, Value = value };
+                    Array.Copy(_entries, i, entries, i + 1, _entries.Length - i);
+                    break;
+                }
+            }
+
+            _entries = entries;
+        }
+
+        private void SetValueInternal(AvaloniaProperty property, object value)
+        {
+            _entries[TryFindEntry(property.Id).Item1].Value = value;
+        }
+
+        private (int, bool) TryFindEntry(int propertyId)
+        {
+            if (_entries.Length <= 20)
+            {
+                // For small lists, we use an optimized linear search. Since the last item in the list
+                // is always int.MaxValue, we can skip a conditional branch in each iteration.
+                // By unrolling the loop, we can skip another unconditional branch in each iteration.
+
+                if (_entries[0].PropertyId >= propertyId) return (0, _entries[0].PropertyId == propertyId);
+                if (_entries[1].PropertyId >= propertyId) return (1, _entries[1].PropertyId == propertyId);
+                if (_entries[2].PropertyId >= propertyId) return (2, _entries[2].PropertyId == propertyId);
+                if (_entries[3].PropertyId >= propertyId) return (3, _entries[3].PropertyId == propertyId);
+                if (_entries[4].PropertyId >= propertyId) return (4, _entries[4].PropertyId == propertyId);
+                if (_entries[5].PropertyId >= propertyId) return (5, _entries[5].PropertyId == propertyId);
+                if (_entries[6].PropertyId >= propertyId) return (6, _entries[6].PropertyId == propertyId);
+                if (_entries[7].PropertyId >= propertyId) return (7, _entries[7].PropertyId == propertyId);
+                if (_entries[8].PropertyId >= propertyId) return (8, _entries[8].PropertyId == propertyId);
+                if (_entries[9].PropertyId >= propertyId) return (9, _entries[9].PropertyId == propertyId);
+                if (_entries[10].PropertyId >= propertyId) return (10, _entries[10].PropertyId == propertyId);
+                if (_entries[11].PropertyId >= propertyId) return (11, _entries[11].PropertyId == propertyId);
+                if (_entries[12].PropertyId >= propertyId) return (12, _entries[12].PropertyId == propertyId);
+                if (_entries[13].PropertyId >= propertyId) return (13, _entries[13].PropertyId == propertyId);
+                if (_entries[14].PropertyId >= propertyId) return (14, _entries[14].PropertyId == propertyId);
+                if (_entries[15].PropertyId >= propertyId) return (15, _entries[15].PropertyId == propertyId);
+                if (_entries[16].PropertyId >= propertyId) return (16, _entries[16].PropertyId == propertyId);
+                if (_entries[17].PropertyId >= propertyId) return (17, _entries[17].PropertyId == propertyId);
+                if (_entries[18].PropertyId >= propertyId) return (18, _entries[18].PropertyId == propertyId);
+            }
+            else
+            {
+                int low = 0;
+                int high = _entries.Length;
+                int id;
+
+                if (high > 0)
+                {
+                    while (high - low > 3)
+                    {
+                        int pivot = (high + low) / 2;
+                        id = _entries[pivot].PropertyId;
+
+                        if (propertyId == id)
+                            return (pivot, true);
+
+                        if (propertyId <= id)
+                            high = pivot;
+                        else
+                            low = pivot + 1;
+                    }
+
+                    do
+                    {
+                        id = _entries[low].PropertyId;
+
+                        if (id == propertyId)
+                            return (low, true);
+
+                        if (id > propertyId)
+                            break;
+
+                        ++low;
+                    }
+                    while (low < high);
+                }
+            }
+
+            return (0, false);
+        }
     }
 }