From 59c415d0463868c0ae061d741f37ad26810d714f Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 26 Jan 2020 20:35:26 +0100 Subject: [PATCH 1/6] Remove list allocations from the general case of Selector.Match --- src/Avalonia.Styling/Styling/Selector.cs | 29 +++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index af209ea970..b8a67e6d96 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -41,7 +41,9 @@ namespace Avalonia.Styling /// A . public SelectorMatch Match(IStyleable control, bool subscribe = true) { - var inputs = new List>(); + List> inputs = null; + IObservable singleInput = null; + var selector = this; var alwaysThisType = true; var hitCombinator = false; @@ -66,16 +68,37 @@ namespace Avalonia.Styling } else if (match.Result == SelectorMatchResult.Sometimes) { - inputs.Add(match.Activator); + if (inputs != null) + { + inputs.Add(match.Activator); + } + else + { + if (singleInput == null) + { + singleInput = match.Activator; + } + else + { + inputs = new List>(); + + inputs.Add(singleInput); + inputs.Add(match.Activator); + } + } } selector = selector.MovePrevious(); } - if (inputs.Count > 0) + if (inputs != null) { return new SelectorMatch(StyleActivator.And(inputs)); } + else if (singleInput != null) + { + return new SelectorMatch(singleInput); + } else { return alwaysThisType && !hitCombinator ? From 5873057817f5b2dd76d6b1f4f3e00c524d4ecb78 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 26 Jan 2020 20:53:11 +0100 Subject: [PATCH 2/6] Add sanity check. --- src/Avalonia.Styling/Styling/Selector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index b8a67e6d96..52c6ae4027 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; namespace Avalonia.Styling { @@ -68,6 +69,8 @@ namespace Avalonia.Styling } else if (match.Result == SelectorMatchResult.Sometimes) { + Debug.Assert(match.Activator != null); + if (inputs != null) { inputs.Add(match.Activator); From 46781ad9082ccea3aab7950095238003269cbaad Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 26 Jan 2020 20:58:41 +0100 Subject: [PATCH 3/6] Add basic benchmarks for Is and Class selectors. --- .../Styling/SelectorBenchmark.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs diff --git a/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs new file mode 100644 index 0000000000..0ac96c1103 --- /dev/null +++ b/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(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); + } + } +} From d7b07dec023f263933cbbde8f08988616e06366c Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 26 Jan 2020 23:06:22 +0100 Subject: [PATCH 4/6] Replace TypeInfo with Type. --- src/Avalonia.Base/AvaloniaProperty.cs | 4 +-- src/Avalonia.Base/Utilities/TypeUtilities.cs | 32 ++++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index e1d4a23441..b305a9aaa2 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Reactive.Subjects; -using System.Reflection; using Avalonia.Data; using Avalonia.Utilities; @@ -602,7 +600,7 @@ namespace Avalonia return result; } - currentType = currentType.GetTypeInfo().BaseType; + currentType = currentType.BaseType; } _metadataCache[type] = _defaultMetadata; diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 3cf05509d4..d1393a9c0d 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -92,8 +92,7 @@ namespace Avalonia.Utilities /// True if the type accepts null values; otherwise false. public static bool AcceptsNull(Type type) { - var t = type.GetTypeInfo(); - return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>))); + return !type.IsValueType || IsNullableType(type); } /// @@ -119,10 +118,8 @@ namespace Avalonia.Utilities } var from = value.GetType(); - var fromTypeInfo = from.GetTypeInfo(); - var toTypeInfo = to.GetTypeInfo(); - if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) + if (to.IsAssignableFrom(from)) { result = value; return true; @@ -134,7 +131,7 @@ namespace Avalonia.Utilities return true; } - if (toTypeInfo.IsEnum && from == typeof(string)) + if (to.IsEnum && from == typeof(string)) { if (Enum.IsDefined(to, (string)value)) { @@ -143,7 +140,7 @@ namespace Avalonia.Utilities } } - if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum) + if (!from.IsEnum && to.IsEnum) { result = null; @@ -154,7 +151,7 @@ namespace Avalonia.Utilities } } - if (fromTypeInfo.IsEnum && IsNumeric(to)) + if (from.IsEnum && IsNumeric(to)) { try { @@ -223,10 +220,8 @@ namespace Avalonia.Utilities } var from = value.GetType(); - var fromTypeInfo = from.GetTypeInfo(); - var toTypeInfo = to.GetTypeInfo(); - if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) + if (to.IsAssignableFrom(from)) { result = value; return true; @@ -307,9 +302,7 @@ namespace Avalonia.Utilities /// The default value. public static object Default(Type type) { - var typeInfo = type.GetTypeInfo(); - - if (typeInfo.IsValueType) + if (type.IsValueType) { return Activator.CreateInstance(type); } @@ -335,9 +328,11 @@ namespace Avalonia.Utilities return false; } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + Type underlyingType = Nullable.GetUnderlyingType(type); + + if (underlyingType != null) { - return IsNumeric(Nullable.GetUnderlyingType(type)); + return IsNumeric(underlyingType); } else { @@ -352,6 +347,11 @@ namespace Avalonia.Utilities Explicit = 2 } + private static bool IsNullableType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType) { const string implicitName = "op_Implicit"; From 9206e59efb3d64144c828173cf07e82c30f9ad39 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 27 Jan 2020 14:24:18 -0300 Subject: [PATCH 5/6] Check if an exception was thrown before entering the mainloop. --- src/Avalonia.Native/PlatformThreadingInterface.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Native/PlatformThreadingInterface.cs b/src/Avalonia.Native/PlatformThreadingInterface.cs index 5ddaf83ed4..ba8cc83e83 100644 --- a/src/Avalonia.Native/PlatformThreadingInterface.cs +++ b/src/Avalonia.Native/PlatformThreadingInterface.cs @@ -60,6 +60,7 @@ namespace Avalonia.Native public void RunLoop(CancellationToken cancellationToken) { + _exceptionDispatchInfo?.Throw(); var l = new object(); _exceptionCancellationSource = new CancellationTokenSource(); From beea785f30f6010ba7ffd3f2ce5d9f7d785dcadc Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 27 Jan 2020 23:13:10 +0100 Subject: [PATCH 6/6] Implement simple struct wrapping a single value or List. --- .../Utilities/ValueSingleOrList.cs | 94 +++++++++++++++++++ src/Avalonia.Styling/Styling/Selector.cs | 33 ++----- 2 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 src/Avalonia.Base/Utilities/ValueSingleOrList.cs diff --git a/src/Avalonia.Base/Utilities/ValueSingleOrList.cs b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs new file mode 100644 index 0000000000..dc32cedb76 --- /dev/null +++ b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; + +namespace Avalonia.Utilities +{ + /// + /// A list like struct optimized for holding zero or one items. + /// + /// The type of items held in the list. + /// + /// Once more than value has been added to this storage it will switch to using internally. + /// + public ref struct ValueSingleOrList + { + private bool _isSingleSet; + + /// + /// Single contained value. Only valid if is set. + /// + public T Single { get; private set; } + + /// + /// List of values. + /// + public List List { get; private set; } + + /// + /// If this struct is backed by a list. + /// + public bool HasList => List != null; + + /// + /// If this struct contains only single value and storage was not promoted to a list. + /// + public bool IsSingle => List == null && _isSingleSet; + + /// + /// Adds a value. + /// + /// Value to add. + public void Add(T value) + { + if (List != null) + { + List.Add(value); + } + else + { + if (!_isSingleSet) + { + Single = value; + + _isSingleSet = true; + } + else + { + List = new List(); + + List.Add(Single); + List.Add(value); + + Single = default; + } + } + } + + /// + /// Removes a value. + /// + /// Value to remove. + public bool Remove(T value) + { + if (List != null) + { + return List.Remove(value); + } + + if (!_isSingleSet) + { + return false; + } + + if (EqualityComparer.Default.Equals(Single, value)) + { + Single = default; + + _isSingleSet = false; + + return true; + } + + return false; + } + } +} diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index 52c6ae4027..7d4e92baeb 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Avalonia.Utilities; namespace Avalonia.Styling { @@ -42,8 +43,7 @@ namespace Avalonia.Styling /// A . public SelectorMatch Match(IStyleable control, bool subscribe = true) { - List> inputs = null; - IObservable singleInput = null; + ValueSingleOrList> inputs = default; var selector = this; var alwaysThisType = true; @@ -71,40 +71,23 @@ namespace Avalonia.Styling { Debug.Assert(match.Activator != null); - if (inputs != null) - { - inputs.Add(match.Activator); - } - else - { - if (singleInput == null) - { - singleInput = match.Activator; - } - else - { - inputs = new List>(); - - inputs.Add(singleInput); - inputs.Add(match.Activator); - } - } + inputs.Add(match.Activator); } selector = selector.MovePrevious(); } - if (inputs != null) + if (inputs.HasList) { - return new SelectorMatch(StyleActivator.And(inputs)); + return new SelectorMatch(StyleActivator.And(inputs.List)); } - else if (singleInput != null) + else if (inputs.IsSingle) { - return new SelectorMatch(singleInput); + return new SelectorMatch(inputs.Single); } else { - return alwaysThisType && !hitCombinator ? + return alwaysThisType && !hitCombinator ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance; }