Browse Source

Improved performance of value lookup in AvaloniaObject's ValueStore

pull/2209/head
mstr2 8 years ago
parent
commit
c5e4996da2
  1. 16
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  2. 156
      src/Avalonia.Base/ValueStore.cs

16
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -13,6 +13,8 @@ namespace Avalonia
/// </summary> /// </summary>
public class AvaloniaPropertyRegistry public class AvaloniaPropertyRegistry
{ {
private readonly Dictionary<int, AvaloniaProperty> _allProperties =
new Dictionary<int, AvaloniaProperty>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered = private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>(); new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached = private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
@ -148,6 +150,16 @@ namespace Avalonia
return FindRegistered(o.GetType(), name); 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> /// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is registered on a type. /// Checks whether a <see cref="AvaloniaProperty"/> is registered on a type.
/// </summary> /// </summary>
@ -202,7 +214,8 @@ namespace Avalonia
{ {
inner.Add(property.Id, property); inner.Add(property.Id, property);
} }
_allProperties[property.Id] = property;
_registeredCache.Clear(); _registeredCache.Clear();
} }
@ -238,6 +251,7 @@ namespace Avalonia
inner.Add(property.Id, property); inner.Add(property.Id, property);
} }
_allProperties[property.Id] = property;
_attachedCache.Clear(); _attachedCache.Clear();
} }
} }

156
src/Avalonia.Base/ValueStore.cs

@ -7,13 +7,21 @@ namespace Avalonia
{ {
internal class ValueStore : IPriorityValueOwner internal class ValueStore : IPriorityValueOwner
{ {
struct Entry
{
internal int PropertyId;
internal object Value;
}
private readonly AvaloniaObject _owner; private readonly AvaloniaObject _owner;
private readonly Dictionary<AvaloniaProperty, object> _values = private Entry[] _entries;
new Dictionary<AvaloniaProperty, object>();
public ValueStore(AvaloniaObject owner) public ValueStore(AvaloniaObject owner)
{ {
_owner = 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( public IDisposable AddBinding(
@ -23,7 +31,7 @@ namespace Avalonia
{ {
PriorityValue priorityValue; PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v)) if (TryGetValue(property, out var v))
{ {
priorityValue = v as PriorityValue; priorityValue = v as PriorityValue;
@ -31,13 +39,13 @@ namespace Avalonia
{ {
priorityValue = CreatePriorityValue(property); priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue; SetValueInternal(property, priorityValue);
} }
} }
else else
{ {
priorityValue = CreatePriorityValue(property); priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue); AddValueInternal(property, priorityValue);
} }
return priorityValue.Add(source, (int)priority); return priorityValue.Add(source, (int)priority);
@ -47,7 +55,7 @@ namespace Avalonia
{ {
PriorityValue priorityValue; PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v)) if (TryGetValue(property, out var v))
{ {
priorityValue = v as PriorityValue; priorityValue = v as PriorityValue;
@ -55,7 +63,7 @@ namespace Avalonia
{ {
if (priority == (int)BindingPriority.LocalValue) if (priority == (int)BindingPriority.LocalValue)
{ {
_values[property] = Validate(property, value); SetValueInternal(property, Validate(property, value));
Changed(property, priority, v, value); Changed(property, priority, v, value);
return; return;
} }
@ -63,7 +71,7 @@ namespace Avalonia
{ {
priorityValue = CreatePriorityValue(property); priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue; SetValueInternal(property, priorityValue);
} }
} }
} }
@ -76,14 +84,14 @@ namespace Avalonia
if (priority == (int)BindingPriority.LocalValue) if (priority == (int)BindingPriority.LocalValue)
{ {
_values.Add(property, Validate(property, value)); AddValueInternal(property, Validate(property, value));
Changed(property, priority, AvaloniaProperty.UnsetValue, value); Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return; return;
} }
else else
{ {
priorityValue = CreatePriorityValue(property); priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue); AddValueInternal(property, priorityValue);
} }
} }
@ -100,13 +108,22 @@ namespace Avalonia
_owner.PriorityValueChanged(property, priority, oldValue, newValue); _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) public object GetValue(AvaloniaProperty property)
{ {
var result = AvaloniaProperty.UnsetValue; 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; result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
} }
@ -116,12 +133,12 @@ namespace Avalonia
public bool IsAnimating(AvaloniaProperty property) 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) 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; return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
} }
@ -131,7 +148,7 @@ namespace Avalonia
public void Revalidate(AvaloniaProperty property) public void Revalidate(AvaloniaProperty property)
{ {
if (_values.TryGetValue(property, out var value)) if (TryGetValue(property, out var value))
{ {
(value as PriorityValue)?.Revalidate(); (value as PriorityValue)?.Revalidate();
} }
@ -178,5 +195,114 @@ namespace Avalonia
(_deferredSetter = new DeferredSetter<object>()); (_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);
}
} }
} }

Loading…
Cancel
Save