From 2b05319b6bcf46544e4c30607c7c013b44d66cfd Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 00:15:07 +0100 Subject: [PATCH 01/10] Remove unnecessary allocations from the layout loop. --- src/Avalonia.Layout/LayoutManager.cs | 8 +++++++- src/Avalonia.Layout/LayoutQueue.cs | 25 +++++++++++++++++-------- src/Avalonia.Layout/Layoutable.cs | 28 ++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 855f123748..8a639e210b 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -15,9 +15,15 @@ namespace Avalonia.Layout { private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); + private readonly Action _executeLayoutPass; private bool _queued; private bool _running; + public LayoutManager() + { + _executeLayoutPass = QueueLayoutPass; + } + /// public void InvalidateMeasure(ILayoutable control) { @@ -215,7 +221,7 @@ namespace Avalonia.Layout { if (!_queued && !_running) { - Dispatcher.UIThread.Post(ExecuteLayoutPass, DispatcherPriority.Layout); + Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout); _queued = true; } } diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs index eb0e4bd9f3..96f893e7b0 100644 --- a/src/Avalonia.Layout/LayoutQueue.cs +++ b/src/Avalonia.Layout/LayoutQueue.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; namespace Avalonia.Layout { @@ -18,9 +17,11 @@ namespace Avalonia.Layout _shouldEnqueue = shouldEnqueue; } - private Func _shouldEnqueue; - private Queue _inner = new Queue(); - private Dictionary _loopQueueInfo = new Dictionary(); + private readonly Func _shouldEnqueue; + private readonly Queue _inner = new Queue(); + private readonly Dictionary _loopQueueInfo = new Dictionary(); + private readonly List> _notFinalizedBuffer = new List>(); + private int _maxEnqueueCountPerLoop = 1; public int Count => _inner.Count; @@ -60,13 +61,19 @@ namespace Avalonia.Layout public void EndLoop() { - var notfinalized = _loopQueueInfo.Where(v => v.Value.Count >= _maxEnqueueCountPerLoop).ToArray(); + foreach (KeyValuePair info in _loopQueueInfo) + { + if (info.Value.Count >= _maxEnqueueCountPerLoop) + { + _notFinalizedBuffer.Add(info); + } + } _loopQueueInfo.Clear(); - //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle - //one more time as a final attempt - foreach (var item in notfinalized) + // Prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle + // one more time as a final attempt. + foreach (var item in _notFinalizedBuffer) { if (_shouldEnqueue(item.Key)) { @@ -74,6 +81,8 @@ namespace Avalonia.Layout _inner.Enqueue(item.Key); } } + + _notFinalizedBuffer.Clear(); } } } diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index b0757a823d..83fa56be31 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -562,11 +562,18 @@ namespace Avalonia.Layout double width = 0; double height = 0; - foreach (ILayoutable child in this.GetVisualChildren()) + var visualCount = VisualChildren.Count; + + for (var i = 0; i < visualCount; i++) { - child.Measure(availableSize); - width = Math.Max(width, child.DesiredSize.Width); - height = Math.Max(height, child.DesiredSize.Height); + IVisual visual = VisualChildren[i]; + + if (visual is ILayoutable layoutable) + { + layoutable.Measure(availableSize); + width = Math.Max(width, layoutable.DesiredSize.Width); + height = Math.Max(height, layoutable.DesiredSize.Height); + } } return new Size(width, height); @@ -658,9 +665,18 @@ namespace Avalonia.Layout /// The actual size used. protected virtual Size ArrangeOverride(Size finalSize) { - foreach (ILayoutable child in this.GetVisualChildren().OfType()) + var arrangeRect = new Rect(finalSize); + + var visualCount = VisualChildren.Count; + + for (var i = 0; i < visualCount; i++) { - child.Arrange(new Rect(finalSize)); + IVisual visual = VisualChildren[i]; + + if (visual is ILayoutable layoutable) + { + layoutable.Arrange(arrangeRect); + } } return finalSize; From 5ee8b0180847cad69238663dbbc6a1e63cfd3e1c Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 00:23:38 +0100 Subject: [PATCH 02/10] Fix typo. --- src/Avalonia.Layout/LayoutManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 8a639e210b..1792655a13 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -21,7 +21,7 @@ namespace Avalonia.Layout public LayoutManager() { - _executeLayoutPass = QueueLayoutPass; + _executeLayoutPass = ExecuteLayoutPass; } /// From 8eb0f4127a21bf4da4bb783334c2ee0f3c80941b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 16:18:12 +0100 Subject: [PATCH 03/10] Save up to two value lookups when applying layout constraints. --- src/Avalonia.Layout/LayoutHelper.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs index d77cc269f9..cfb4b14b1c 100644 --- a/src/Avalonia.Layout/LayoutHelper.cs +++ b/src/Avalonia.Layout/LayoutHelper.cs @@ -21,8 +21,11 @@ namespace Avalonia.Layout /// The control's size. public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints) { - double width = (control.Width > 0) ? control.Width : constraints.Width; - double height = (control.Height > 0) ? control.Height : constraints.Height; + var controlWidth = control.Width; + var controlHeight = control.Height; + + double width = (controlWidth > 0) ? controlWidth : constraints.Width; + double height = (controlHeight > 0) ? controlHeight : constraints.Height; width = Math.Min(width, control.MaxWidth); width = Math.Max(width, control.MinWidth); height = Math.Min(height, control.MaxHeight); From 3e3eddca87e08d586b2ff6a7b00a9b9ed979a115 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 16:19:25 +0100 Subject: [PATCH 04/10] Save a few value lookups during arrange and measure. --- src/Avalonia.Layout/Layoutable.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 83fa56be31..acd63bb542 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -518,17 +518,25 @@ namespace Avalonia.Layout var width = measured.Width; var height = measured.Height; - if (!double.IsNaN(Width)) { - width = Width; + double widthCache = Width; + + if (!double.IsNaN(widthCache)) + { + width = widthCache; + } } width = Math.Min(width, MaxWidth); width = Math.Max(width, MinWidth); - if (!double.IsNaN(Height)) { - height = Height; + double heightCache = Height; + + if (!double.IsNaN(heightCache)) + { + height = Height; + } } height = Math.Min(height, MaxHeight); @@ -601,6 +609,7 @@ namespace Avalonia.Layout var verticalAlignment = VerticalAlignment; var size = availableSizeMinusMargins; var scale = GetLayoutScale(); + var useLayoutRounding = UseLayoutRounding; if (horizontalAlignment != HorizontalAlignment.Stretch) { @@ -614,7 +623,7 @@ namespace Avalonia.Layout size = LayoutHelper.ApplyLayoutConstraints(this, size); - if (UseLayoutRounding) + if (useLayoutRounding) { size = new Size( Math.Ceiling(size.Width * scale) / scale, @@ -648,7 +657,7 @@ namespace Avalonia.Layout break; } - if (UseLayoutRounding) + if (useLayoutRounding) { originX = Math.Floor(originX * scale) / scale; originY = Math.Floor(originY * scale) / scale; From d0d4bec96ba0a6c73ea017c7c9affe8446060a5d Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 16:22:37 +0100 Subject: [PATCH 05/10] Remove expensive Contract calls from the hot path. --- src/Avalonia.Base/AvaloniaObject.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 2450f1a3a1..0499907ab8 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -210,7 +210,11 @@ namespace Avalonia /// The value. public object GetValue(AvaloniaProperty property) { - Contract.Requires(property != null); + if (property is null) + { + throw new ArgumentNullException(nameof(property)); + } + VerifyAccess(); if (property.IsDirect) @@ -231,7 +235,10 @@ namespace Avalonia /// The value. public T GetValue(AvaloniaProperty property) { - Contract.Requires(property != null); + if (property is null) + { + throw new ArgumentNullException(nameof(property)); + } return (T)GetValue((AvaloniaProperty)property); } From 268a209889725861d2c84ae974c8439be7df5f2f Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 16:23:31 +0100 Subject: [PATCH 06/10] Implement fast metadata path for properties that have no metadata overrides. --- src/Avalonia.Base/AvaloniaProperty.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 56ad241187..d4e416d229 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -28,6 +28,8 @@ namespace Avalonia private readonly Dictionary _metadata; private readonly Dictionary _metadataCache = new Dictionary(); + private bool _hasMetadataOverrides; + /// /// Initializes a new instance of the class. /// @@ -446,16 +448,28 @@ namespace Avalonia /// public PropertyMetadata GetMetadata(Type type) { - Contract.Requires(type != null); + if (!_hasMetadataOverrides) + { + return _defaultMetadata; + } - PropertyMetadata result; - Type currentType = type; + return GetMetadataWithOverrides(type); + } - if (_metadataCache.TryGetValue(type, out result)) + private PropertyMetadata GetMetadataWithOverrides(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (_metadataCache.TryGetValue(type, out PropertyMetadata result)) { return result; } + Type currentType = type; + while (currentType != null) { if (_metadata.TryGetValue(currentType, out result)) @@ -535,6 +549,8 @@ namespace Avalonia metadata.Merge(baseMetadata, this); _metadata.Add(type, metadata); _metadataCache.Clear(); + + _hasMetadataOverrides = true; } [DebuggerHidden] From 798bde1835e9902b68f7fac332467d191979d811 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 16:30:45 +0100 Subject: [PATCH 07/10] Cache visual children getter result. --- src/Avalonia.Layout/Layoutable.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index acd63bb542..1dbe4652c0 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -570,11 +570,12 @@ namespace Avalonia.Layout double width = 0; double height = 0; - var visualCount = VisualChildren.Count; + var visualChildren = VisualChildren; + var visualCount = visualChildren.Count; for (var i = 0; i < visualCount; i++) { - IVisual visual = VisualChildren[i]; + IVisual visual = visualChildren[i]; if (visual is ILayoutable layoutable) { @@ -676,11 +677,12 @@ namespace Avalonia.Layout { var arrangeRect = new Rect(finalSize); - var visualCount = VisualChildren.Count; + var visualChildren = VisualChildren; + var visualCount = visualChildren.Count; for (var i = 0; i < visualCount; i++) { - IVisual visual = VisualChildren[i]; + IVisual visual = visualChildren[i]; if (visual is ILayoutable layoutable) { From a327610b6d4200734e33b3838c079c2e63ca3a2b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 17:58:29 +0100 Subject: [PATCH 08/10] Disable metadata optimizations for properties with different owner. --- src/Avalonia.Base/AvaloniaProperty.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index d4e416d229..f9c891b65d 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -94,6 +94,9 @@ namespace Avalonia Id = source.Id; _defaultMetadata = source._defaultMetadata; + // Properties that have different owner can't use fast path for metadata. + _hasMetadataOverrides = true; + if (metadata != null) { _metadata.Add(ownerType, metadata); From db74e6c021764a7170d2a267458fbf4619350ebc Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 18:28:10 +0100 Subject: [PATCH 09/10] Reformat. --- src/Avalonia.Base/AvaloniaProperty.cs | 62 +++++++++++++-------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index f9c891b65d..ac7d2c60af 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -459,37 +459,6 @@ namespace Avalonia return GetMetadataWithOverrides(type); } - private PropertyMetadata GetMetadataWithOverrides(Type type) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (_metadataCache.TryGetValue(type, out PropertyMetadata result)) - { - return result; - } - - Type currentType = type; - - while (currentType != null) - { - if (_metadata.TryGetValue(currentType, out result)) - { - _metadataCache[type] = result; - - return result; - } - - currentType = currentType.GetTypeInfo().BaseType; - } - - _metadataCache[type] = _defaultMetadata; - - return _defaultMetadata; - } - /// /// Checks whether the is valid for the property. /// @@ -556,6 +525,37 @@ namespace Avalonia _hasMetadataOverrides = true; } + private PropertyMetadata GetMetadataWithOverrides(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (_metadataCache.TryGetValue(type, out PropertyMetadata result)) + { + return result; + } + + Type currentType = type; + + while (currentType != null) + { + if (_metadata.TryGetValue(currentType, out result)) + { + _metadataCache[type] = result; + + return result; + } + + currentType = currentType.GetTypeInfo().BaseType; + } + + _metadataCache[type] = _defaultMetadata; + + return _defaultMetadata; + } + [DebuggerHidden] private static Func Cast(Func f) where TOwner : IAvaloniaObject From c864f5592ce0dc80b8b5809ea35d283d9c8f98ad Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 3 Nov 2019 18:29:58 +0100 Subject: [PATCH 10/10] Fix overlooked assignment. --- src/Avalonia.Layout/Layoutable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 1dbe4652c0..4732808b91 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -535,7 +535,7 @@ namespace Avalonia.Layout if (!double.IsNaN(heightCache)) { - height = Height; + height = heightCache; } }