Browse Source

Merge pull request #3474 from MarchingCube/alloc-selector-match

Remove list allocations from the general case of Selector.Match
pull/3482/head
Nikita Tsukanov 6 years ago
committed by GitHub
parent
commit
43f178552e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 94
      src/Avalonia.Base/Utilities/ValueSingleOrList.cs
  2. 17
      src/Avalonia.Styling/Styling/Selector.cs
  3. 52
      tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs

94
src/Avalonia.Base/Utilities/ValueSingleOrList.cs

@ -0,0 +1,94 @@
using System.Collections.Generic;
namespace Avalonia.Utilities
{
/// <summary>
/// A list like struct optimized for holding zero or one items.
/// </summary>
/// <typeparam name="T">The type of items held in the list.</typeparam>
/// <remarks>
/// Once more than value has been added to this storage it will switch to using <see cref="List"/> internally.
/// </remarks>
public ref struct ValueSingleOrList<T>
{
private bool _isSingleSet;
/// <summary>
/// Single contained value. Only valid if <see cref="IsSingle"/> is set.
/// </summary>
public T Single { get; private set; }
/// <summary>
/// List of values.
/// </summary>
public List<T> List { get; private set; }
/// <summary>
/// If this struct is backed by a list.
/// </summary>
public bool HasList => List != null;
/// <summary>
/// If this struct contains only single value and storage was not promoted to a list.
/// </summary>
public bool IsSingle => List == null && _isSingleSet;
/// <summary>
/// Adds a value.
/// </summary>
/// <param name="value">Value to add.</param>
public void Add(T value)
{
if (List != null)
{
List.Add(value);
}
else
{
if (!_isSingleSet)
{
Single = value;
_isSingleSet = true;
}
else
{
List = new List<T>();
List.Add(Single);
List.Add(value);
Single = default;
}
}
}
/// <summary>
/// Removes a value.
/// </summary>
/// <param name="value">Value to remove.</param>
public bool Remove(T value)
{
if (List != null)
{
return List.Remove(value);
}
if (!_isSingleSet)
{
return false;
}
if (EqualityComparer<T>.Default.Equals(Single, value))
{
Single = default;
_isSingleSet = false;
return true;
}
return false;
}
}
}

17
src/Avalonia.Styling/Styling/Selector.cs

@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Utilities;
namespace Avalonia.Styling
{
@ -41,7 +43,8 @@ namespace Avalonia.Styling
/// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(IStyleable control, bool subscribe = true)
{
var inputs = new List<IObservable<bool>>();
ValueSingleOrList<IObservable<bool>> inputs = default;
var selector = this;
var alwaysThisType = true;
var hitCombinator = false;
@ -66,19 +69,25 @@ namespace Avalonia.Styling
}
else if (match.Result == SelectorMatchResult.Sometimes)
{
Debug.Assert(match.Activator != null);
inputs.Add(match.Activator);
}
selector = selector.MovePrevious();
}
if (inputs.Count > 0)
if (inputs.HasList)
{
return new SelectorMatch(StyleActivator.And(inputs.List));
}
else if (inputs.IsSingle)
{
return new SelectorMatch(StyleActivator.And(inputs));
return new SelectorMatch(inputs.Single);
}
else
{
return alwaysThisType && !hitCombinator ?
return alwaysThisType && !hitCombinator ?
SelectorMatch.AlwaysThisType :
SelectorMatch.AlwaysThisInstance;
}

52
tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs

@ -0,0 +1,52 @@
using Avalonia.Controls;
using Avalonia.Styling;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Styling
{
[MemoryDiagnoser]
public class SelectorBenchmark
{
private readonly Control _notMatchingControl;
private readonly Calendar _matchingControl;
private readonly Selector _isCalendarSelector;
private readonly Selector _classSelector;
public SelectorBenchmark()
{
_notMatchingControl = new Control();
_matchingControl = new Calendar();
const string className = "selector-class";
_matchingControl.Classes.Add(className);
_isCalendarSelector = Selectors.Is<Calendar>(null);
_classSelector = Selectors.Class(null, className);
}
[Benchmark]
public SelectorMatch IsSelector_NoMatch()
{
return _isCalendarSelector.Match(_notMatchingControl);
}
[Benchmark]
public SelectorMatch IsSelector_Match()
{
return _isCalendarSelector.Match(_matchingControl);
}
[Benchmark]
public SelectorMatch ClassSelector_NoMatch()
{
return _classSelector.Match(_notMatchingControl);
}
[Benchmark]
public SelectorMatch ClassSelector_Match()
{
return _classSelector.Match(_matchingControl);
}
}
}
Loading…
Cancel
Save