Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout performance improvements #18315

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

MrJul
Copy link
Member

@MrJul MrJul commented Feb 25, 2025

What does the pull request do?

This PR consists of a series of micro optimizations to Measure() and Arrange(), reducing the number of branches and operations.
These changes result in a measurable reduction in layout times.

For example, our Measure micro-benchmark, which measures and arranges a few thousands StackPanels and Buttons, goes from 1.847 ms before this PR to 1.421 ms after (.NET 8, Windows, x64), nearly a 30% increase in this specific case.

This doesn't mean that users should expect a 30% increase overall. In practice, this depends on the visual tree complexity and controls used. For example, TextBlock is usually bottlenecked by text layouting. Also, it's important to keep in mind that layouting is only a part of the whole process of rendering controls to the screen.

Changes

Layoutable.MeasureCore

  • The internal MinMax struct computes the expected min/max of a dimension (Width or Height), but the result weren't reused in MeasureCore like in WPF, effectively doing the work twice. This has been changed.
  • Math.Min/Max calls have been replaced with single comparisons. In MeasureCore, one of the two operands is always the result and we don't care about NaN or +0/-0 ordering here.

LayoutHelper.GetLayoutScale

  • This method was validating the returned scaling from the TopLevel for each measured visual. The validation has been moved to TopLevel, once, instead.
  • If the layout scaling is close to 1.0, it's normalized to exactly 1.0 in the TopLevel implementation. Thanks to that, we can easily check for scaling == 1.0 directly later instead of using the more expensive Math.IsOne() everywhere.

LayoutHelper.RoundLayoutSize/Thickness

The complexity of these methods – used when UseLayoutRounding is enabled (the default) – has been reduced:

  • Internal overloads taking a single DPI scale have been added since all callers were always passing the same value for scaleX and scaleY.
  • A single branch is now used to check the scale for all values instead of one branch per value.
  • The scale is now checked for 1.0 exactly instead of using MathUtilities.IsOne(), since it's guaranteed to have been normalized by the TopLevel (see above).
  • NaN, and Double.MaxValue aren't checked anymore. The DPI scale is guaranteed to be valid. The only way to get to infinity is to have an invalid value in the first place (or a scale so large that nothing would render correctly anyways).

Visual.VisualRoot

This property is accessed for each measure, arrange, bounds invalidation, visibility change and more...
It's been simplified to a single unconditional field access, set on appropriate paths, instead of always checking for a self implementation of IRenderRoot.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.3.999-cibuild0055145-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]


namespace Avalonia.Layout;

internal struct MinMax
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was ported from WPF and is internal, but the naming doesn't seem right to me.

Instead of MinMax I would expect something like Range.

However, the best name is something more like SizeConstraints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants