From 0db8d5a2d29bddbea21f919a69f3f827f1bf4933 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 21 Nov 2022 12:24:39 +0100 Subject: [PATCH] Refactored style attach benchmark. Now tries to simulate an application with a lot of styles applied at different points in the logical tree. Make `StyledElement.ApplyStyling` a public API in order to do this. --- src/Avalonia.Base/StyledElement.cs | 6 +- .../Styling/StyleAttachBenchmark.cs | 47 ------- .../Styling/Style_Apply_Detach_Complex.cs | 126 ++++++++++++++++++ 3 files changed, 131 insertions(+), 48 deletions(-) delete mode 100644 tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs create mode 100644 tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index bba9685ed8..2f3f672d54 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -344,10 +344,14 @@ namespace Avalonia /// Applies styling to the control if the control is initialized and styling is not /// already applied. /// + /// + /// The styling system will automatically apply styling when required, so it should not + /// usually be necessary to call this method manually. + /// /// /// A value indicating whether styling is now applied to the control. /// - protected bool ApplyStyling() + public bool ApplyStyling() { if (_initCount == 0 && !_styled) { diff --git a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs deleted file mode 100644 index 7dad517e51..0000000000 --- a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Avalonia.Controls; -using Avalonia.Styling; -using Avalonia.UnitTests; -using BenchmarkDotNet.Attributes; - -namespace Avalonia.Benchmarks.Styling -{ - [MemoryDiagnoser] - public class StyleAttachBenchmark : IDisposable - { - private readonly IDisposable _app; - private readonly TestRoot _root; - private readonly TextBox _control; - - public StyleAttachBenchmark() - { - _app = UnitTestApplication.Start( - TestServices.StyledWindow.With( - renderInterface: new NullRenderingPlatform(), - threadingInterface: new NullThreadingPlatform())); - - _root = new TestRoot(true, null) - { - Renderer = new NullRenderer(), - }; - - _control = new TextBox(); - } - - [Benchmark] - [MethodImpl(MethodImplOptions.NoInlining)] - public void AttachTextBoxStyles() - { - var styles = UnitTestApplication.Current.Styles; - - styles.TryAttach(_control, UnitTestApplication.Current); - ((IStyleable)_control).DetachStyles(); - } - - public void Dispose() - { - _app.Dispose(); - } - } -} diff --git a/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs b/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs new file mode 100644 index 0000000000..fa8ad00bf8 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.UnitTests; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Styling +{ + [MemoryDiagnoser] + public class Style_Apply_Detach_Complex : IDisposable + { + private readonly IDisposable _app; + private readonly TestRoot _root; + private readonly TextBox _control; + + public Style_Apply_Detach_Complex() + { + _app = UnitTestApplication.Start( + TestServices.StyledWindow.With( + renderInterface: new NullRenderingPlatform(), + threadingInterface: new NullThreadingPlatform())); + + // Simulate an application with a lot of styles by creating a tree of nested panels, + // each with a bunch of styles applied. + var (rootPanel, leafPanel) = CreateNestedPanels(10); + + // We're benchmarking how long it takes to apply styles to a TextBox in this situation. + _control = new TextBox(); + leafPanel.Children.Add(_control); + + _root = new TestRoot(true, rootPanel) + { + Renderer = new NullRenderer(), + }; + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void Apply_Detach_Styles() + { + // Styles will have already been attached when attached to the logical tree, so remove + // the styles first. + if ((string)_control.Tag != "TextBox") + throw new Exception("Invalid benchmark state"); + + ((IStyleable)_control).DetachStyles(); + + if (_control.Tag is not null) + throw new Exception("Invalid benchmark state"); + + // Then re-apply the styles. + _control.ApplyStyling(); + } + + public void Dispose() + { + _app.Dispose(); + } + + private static (Panel, Panel) CreateNestedPanels(int count) + { + var root = new Panel(); + var last = root; + + for (var i = 0; i < count; ++i) + { + var panel = new Panel(); + panel.Styles.AddRange(CreateStyles()); + last.Children.Add(panel); + last = panel; + } + + return (root, last); + } + + private static IEnumerable CreateStyles() + { + var types = new[] + { + typeof(Border), + typeof(Button), + typeof(ButtonSpinner), + typeof(Carousel), + typeof(CheckBox), + typeof(ComboBox), + typeof(ContentControl), + typeof(Expander), + typeof(ItemsControl), + typeof(Label), + typeof(ListBox), + typeof(ProgressBar), + typeof(RadioButton), + typeof(RepeatButton), + typeof(ScrollViewer), + typeof(Slider), + typeof(Spinner), + typeof(SplitView), + typeof(TextBox), + typeof(ToggleSwitch), + typeof(TreeView), + typeof(Viewbox), + typeof(Window), + }; + + foreach (var type in types) + { + yield return new Style(x => x.OfType(type)) + { + Setters = { new Setter(Control.TagProperty, type.Name) } + }; + + yield return new Style(x => x.OfType(type).Class("foo")) + { + Setters = { new Setter(Control.TagProperty, type.Name + " foo") } + }; + + yield return new Style(x => x.OfType(type).Class("bar")) + { + Setters = { new Setter(Control.TagProperty, type.Name + " bar") } + }; + } + } + } +}