From d9d208b935896ab2de740856ca77ef2261c5ec8e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 5 Mar 2023 17:49:45 +0100 Subject: [PATCH] Add benchmark for Or selector and avoid extra virtual calls getting list count. --- .../Styling/DescendentSelector.cs | 2 +- src/Avalonia.Base/Styling/OrSelector.cs | 27 +++++------ .../Styling/TypeNameAndClassSelector.cs | 9 +--- .../Properties/launchSettings.json | 15 ++++++ .../Styling/SelectorBenchmark.cs | 48 ++++++++++++++++++- 5 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 tests/Avalonia.Benchmarks/Properties/launchSettings.json diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index a056f3ba8b..77ae0f2877 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -13,7 +13,7 @@ namespace Avalonia.Styling public DescendantSelector(Selector? parent) { - _parent = parent ?? throw new InvalidOperationException("Descendant selector must be preceeded by a selector."); + _parent = parent ?? throw new InvalidOperationException("Descendant selector must be preceded by a selector."); } /// diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index 3b0aa03492..53e4baa2c4 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -10,7 +10,7 @@ namespace Avalonia.Styling /// /// The OR style selector. /// - internal class OrSelector : Selector + internal sealed class OrSelector : Selector { private readonly IReadOnlyList _selectors; private string? _selectorString; @@ -42,18 +42,7 @@ namespace Avalonia.Styling public override bool IsCombinator => false; /// - public override Type? TargetType - { - get - { - if (_targetType == null) - { - _targetType = EvaluateTargetType(); - } - - return _targetType; - } - } + public override Type? TargetType => _targetType ??= EvaluateTargetType(); /// public override string ToString(Style? owner) @@ -71,7 +60,9 @@ namespace Avalonia.Styling var activators = new OrActivatorBuilder(); var neverThisInstance = false; - for (var i = 0; i < _selectors.Count; i++) + var count = _selectors.Count; + + for (var i = 0; i < count; i++) { var match = _selectors[i].Match(control, parent, subscribe); @@ -108,7 +99,9 @@ namespace Avalonia.Styling internal override void ValidateNestingSelector(bool inControlTheme) { - for (var i = 0; i < _selectors.Count; i++) + var count = _selectors.Count; + + for (var i = 0; i < count; i++) { _selectors[i].ValidateNestingSelector(inControlTheme); } @@ -118,7 +111,9 @@ namespace Avalonia.Styling { Type? result = null; - for (var i = 0; i < _selectors.Count; i++) + var count = _selectors.Count; + + for (var i = 0; i < count; i++) { var selector = _selectors[i]; if (selector.TargetType == null) diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index f8670cfdb3..4e0d37479b 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -11,7 +11,7 @@ namespace Avalonia.Styling /// A selector that matches the common case of a type and/or name followed by a collection of /// style classes and pseudoclasses. /// - internal class TypeNameAndClassSelector : Selector + internal sealed class TypeNameAndClassSelector : Selector { private readonly Selector? _previous; private List? _classes; @@ -85,12 +85,7 @@ namespace Avalonia.Styling /// public override string ToString(Style? owner) { - if (_selectorString == null) - { - _selectorString = BuildSelectorString(owner); - } - - return _selectorString; + return _selectorString ??= BuildSelectorString(owner); } /// diff --git a/tests/Avalonia.Benchmarks/Properties/launchSettings.json b/tests/Avalonia.Benchmarks/Properties/launchSettings.json new file mode 100644 index 0000000000..619d635ebf --- /dev/null +++ b/tests/Avalonia.Benchmarks/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "Avalonia.Benchmarks": { + "commandName": "Project" + }, + "Avalonia.Benchmarks (in-process)": { + "commandName": "Project", + "commandLineArgs": "--inprocess" + }, + "Avalonia.Benchmarks (debug)": { + "commandName": "Project", + "commandLineArgs": "--debug" + } + } +} diff --git a/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs index 0ac96c1103..11bc5ce35f 100644 --- a/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls; +using System; +using Avalonia.Controls; using Avalonia.Styling; using BenchmarkDotNet.Attributes; @@ -11,6 +12,8 @@ namespace Avalonia.Benchmarks.Styling private readonly Calendar _matchingControl; private readonly Selector _isCalendarSelector; private readonly Selector _classSelector; + private readonly Selector _orSelectorTwo; + private readonly Selector _orSelectorFive; public SelectorBenchmark() { @@ -23,6 +26,14 @@ namespace Avalonia.Benchmarks.Styling _isCalendarSelector = Selectors.Is(null); _classSelector = Selectors.Class(null, className); + + _orSelectorTwo = Selectors.Or(new AlwaysMatchSelector(), new AlwaysMatchSelector()); + _orSelectorFive = Selectors.Or( + new AlwaysMatchSelector(), + new AlwaysMatchSelector(), + new AlwaysMatchSelector(), + new AlwaysMatchSelector(), + new AlwaysMatchSelector()); } [Benchmark] @@ -48,5 +59,40 @@ namespace Avalonia.Benchmarks.Styling { return _classSelector.Match(_matchingControl); } + + [Benchmark] + public SelectorMatch OrSelector_One_Match() + { + return _orSelectorTwo.Match(_matchingControl); + } + + [Benchmark] + public SelectorMatch OrSelector_Five_Match() + { + return _orSelectorFive.Match(_matchingControl); + } + } + + internal class AlwaysMatchSelector : Selector + { + public override bool InTemplate => false; + + public override bool IsCombinator => false; + + public override Type TargetType => null; + + public override string ToString(Style owner) + { + return "Always"; + } + + protected override SelectorMatch Evaluate(StyledElement control, IStyle parent, bool subscribe) + { + return SelectorMatch.AlwaysThisType; + } + + protected override Selector MovePrevious() => null; + + protected override Selector MovePreviousOrParent() => null; } }