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

Value store v3.0 #8600

Merged
merged 115 commits into from
Nov 18, 2022
Merged

Value store v3.0 #8600

merged 115 commits into from
Nov 18, 2022

Conversation

grokys
Copy link
Member

@grokys grokys commented Jul 26, 2022

What does the pull request do?

This PR implements a new value store for AvaloniaObject. Previously, each property value was stored in a dictionary inside ValueStore which mapped property -> value, where value changed type depending on what was stored (single local value, single binding, multiple values of differing priorities).

This PR changes ValueStore to store property values in a different manner;

  • A dictionary mapping the property to an EffectiveValue<T> containing the effective value and base value + priorities
  • Local values are stored directly to the EffectiveValue<T> for the property
  • A collection of "Frames" ordered by priority. Frames are currently one of two types:
    • ImmediateValueFrame: values set by SetValue or AddBinding with non-LocalValue priority.
    • StyleInstance: a frame containing the Setters for a style
  • Each frame is a collection of IValueEntrys
  • Inherited values work by storing a reference to the nearest ancestor which has inherited values set in its effective value collection

Some improvements the new value store enables:

  • Frames can be added to multiple ValueStores meaning that Styles which don't have activators (and ControlThemes) can be instanced just once instead of needing to be instanced on each AvaloniaObject separately.
  • Setter is an IValueEntry meaning that it can be stored directly a frame unless it needs to be explicitly instanced on each control
  • Frames can be added and removed separately, meaning that a ControlTheme can be changed without affecting the rest of the styles applied to it (this isn't actually implemented yet - will require a change to our BindingPrioritys in order to distinguish between control themes and styles)
  • We don't need to traverse the whole tree to read an inherited value - only the ancestors in the tree which set inherited values
  • Direct property bindings can be stored in the same collection as LocalValue styled property bindings, removing the need for 2 lists per AvaloniaObject
  • Uses a minimum amount of virtual methods and generic methods as these can slow things down a lot (virtual methods at runtime, generic methods at JIT time)

Benchmarks

| Slower                                                                          | diff/base | Base Median (ns) | Diff Median (ns) | Modality|
| ------------------------------------------------------------------------------- | ---------:| ----------------:| ----------------:| --------:|
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValue.GetDefaultValues               |      1.17 |         14114.19 |         16473.92 |         |
| Avalonia.Benchmarks.Styling.ResourceBenchmarks.FindNotExistingResource          |      1.12 |        809786.47 |        909711.54 |         |
| Avalonia.Benchmarks.Base.AvaloniaObjectInitializationBenchmark.InitializeButton |      1.12 |           217.07 |           243.48 |         |

| Faster                                                                           | base/diff | Base Median (ns) | Diff Median (ns) | Modality|
| -------------------------------------------------------------------------------- | ---------:| ----------------:| ----------------:| --------:|
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValueInherited.GetInheritedValues(Dep |     18.94 |        393126.81 |         20757.63 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValueInherited.GetInheritedValues(Dep |      9.65 |        199979.01 |         20728.13 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValueInherited.GetInheritedValues(Dep |      5.54 |        117037.06 |         21110.74 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 50,  |      4.22 |         24211.55 |          5732.49 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 50,  |      4.18 |         24524.41 |          5869.84 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 50,  |      3.50 |         26346.97 |          7524.48 |         |
| Avalonia.Benchmarks.Styling.Style_ClassSelector.Apply_Toggle                     |      3.30 |          1618.19 |           489.66 |         |
| Avalonia.Benchmarks.Styling.Style_ClassSelector.Apply                            |      3.18 |           866.49 |           272.54 |         |
| Avalonia.Benchmarks.Styling.Style_ClassSelector.Apply_Detach                     |      3.02 |          1392.40 |           461.58 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValue.Get_Local_Values                |      2.81 |         39732.18 |         14129.47 |         |
| Avalonia.Benchmarks.Base.AvaloniaObjectBenchmark.BindIntProperty                 |      2.75 |         14234.11 |          5171.49 |         |
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Coerced_Int_Property_Local |      2.62 |         17512.68 |          6680.21 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 5, N |      2.60 |          2819.56 |          1085.22 |         |
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Int_Property_Multiple_Prio |      2.54 |        254065.91 |         99880.78 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValue.Get_Local_Values_With_Style_Val |      2.52 |         35301.84 |         13988.89 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_SetValue.Set_Local_Values_With_Style_Val |      2.51 |        212792.09 |         84756.34 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 5, N |      2.40 |          3016.59 |          1256.49 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValueInherited.GetInheritedValues(Dep |      2.22 |         47551.68 |         21380.50 |         |
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Int_Property_LocalValue    |      2.13 |         13529.07 |          6364.65 |         |
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Validated_Int_Property_Loc |      2.05 |         13719.47 |          6681.29 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_Binding.Fire_LocalValue_Bindings_With_St |      2.05 |        211422.13 |        103336.03 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValueInherited.GetInheritedValues(Dep |      2.05 |         41533.68 |         20306.47 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetValueInherited.GetInheritedValues(Dep |      2.01 |         42798.10 |         21332.83 |         |
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Int_Property_TemplatedPare |      1.88 |         38559.38 |         20458.80 |         |
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Bind_Int_Property_Multiple_Pri |      1.84 |         50798.83 |         27596.72 |         |
| Avalonia.Benchmarks.Rendering.ShapeRendering.Render_Line_WithFill                |      1.78 |            83.03 |            46.72 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_SetValue.SetValues                       |      1.66 |        140613.59 |         84628.17 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 5, N |      1.65 |          4733.56 |          2863.75 |         |
| Avalonia.Benchmarks.Base.AvaloniaObjectBenchmark.ClearAndSetIntProperty          |      1.63 |           262.61 |           161.28 |         |
| Avalonia.Benchmarks.Styling.Style_NonActive.Toggle_NonActive_Style_Activation    |      1.59 |         50500.13 |         31824.36 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_Binding.Setup_Dispose_LocalValue_Binding |      1.45 |        348959.30 |        240953.58 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 1, N |      1.43 |           917.89 |           640.52 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetObservable.PropertyChangedSubscriptio |      1.42 |        182705.85 |        129079.87 |         |
| Avalonia.Benchmarks.Base.DirectPropertyBenchmark.SetAndRaiseSimple               |      1.37 |          3833.04 |          2800.24 |         |
| Avalonia.Benchmarks.Base.DirectPropertyBenchmark.SetAndRaiseOriginal             |      1.36 |          3631.91 |          2664.83 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 1, N |      1.35 |          1097.20 |           811.80 |         |
| Avalonia.Benchmarks.Rendering.ShapeRendering.Render_Line_WithFillAndStroke       |      1.35 |           322.64 |           239.44 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_GetObservable.GetObservables             |      1.35 |        408508.36 |        303470.05 |         |
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Bind_Int_Property_LocalValue   |      1.33 |          7343.93 |          5529.49 |         |
| Avalonia.Benchmarks.Visuals.VisualAffectsRenderBenchmarks.SetPropertyThatAffects |      1.27 |           455.90 |           359.53 |         |
| Avalonia.Benchmarks.Themes.FluentBenchmark.RepeatButton                          |      1.26 |        208442.03 |        165051.30 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_Construct.Construct_And_Set_Values       |      1.24 |          2008.03 |          1618.78 |         |
| Avalonia.Benchmarks.Data.BindingsBenchmark.UpdateTwoWayBinding_Via_Binding       |      1.24 |        146089.96 |        117788.46 |         |
| Avalonia.Benchmarks.Layout.ControlsBenchmark.CreateButton                        |      1.21 |        127293.46 |        105071.62 |         |
| Avalonia.Benchmarks.Rendering.ShapeRendering.Render_Line_WithStroke              |      1.20 |           305.47 |           254.36 |         |
| Avalonia.Benchmarks.Layout.ControlsBenchmark.CreateCalendarWithLoaded            |      1.20 |      17490624.00 |      14626879.00 |         |
| Avalonia.Benchmarks.Layout.ControlsBenchmark.CreateCalendar                      |      1.19 |      17434331.00 |      14619440.00 |         |
| Avalonia.Benchmarks.Styling.StyleAttachBenchmark.AttachTextBoxStyles             |      1.14 |          1386.99 |          1214.21 |         |
| Avalonia.Benchmarks.Base.AvaloniaObject_Binding.Fire_LocalValue_Bindings         |      1.14 |        121065.15 |        106660.97 |         |
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 1, N |      1.12 |          2726.70 |          2436.82 |         |

| MoreAllocations                                                                  | diff/base | Base Allocated (B) | Diff Allocated (B)|
| -------------------------------------------------------------------------------- | ---------:| ------------------:| ------------------:|
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Int_Property_Multiple_Prio |      1.51 |           69392.00 |          104600.00|
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Int_Property_TemplatedPare |      1.43 |           20800.00 |           29672.00|
| Avalonia.Benchmarks.Styling.Style_Activation.Toggle_Style_Activation_Via_Class   |      1.40 |           16000.00 |           22400.00|
| Avalonia.Benchmarks.Base.AvaloniaObjectInitializationBenchmark.InitializeButton  |      1.09 |             928.00 |            1016.00|
| Avalonia.Benchmarks.Base.AvaloniaObject_Construct.Construct_And_Set_Values       |      1.07 |            2408.00 |            2584.00|
| Avalonia.Benchmarks.Data.BindingsBenchmark.TwoWayBinding_Via_Binding             |      1.04 |            2867.00 |            2981.00|
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Bind_Int_Property_LocalValue   |      1.03 |            6768.00 |            7000.00|
| Avalonia.Benchmarks.Visuals.Media.PathMarkupParserTests.Parse_Large_Path         |      1.03 |            3608.00 |            3712.00|
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Int_Property_LocalValue    |      1.01 |            6616.00 |            6712.00|
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Validated_Int_Property_Loc |      1.01 |            6616.00 |            6712.00|

| LessAllocations                                                                  | base/diff | Base Allocated (B) | Diff Allocated (B)|
| -------------------------------------------------------------------------------- | ---------:| ------------------:| ------------------:|
| Avalonia.Benchmarks.Styling.Style_NonActive.Toggle_NonActive_Style_Activation    |      0.00 |           16000.00 |               0.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 50,  |      0.11 |           20384.00 |            2320.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 50,  |      0.11 |           20384.00 |            2320.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 50,  |      0.11 |           20384.00 |            2320.00|
| Avalonia.Benchmarks.Base.AvaloniaObject_Binding.Setup_Dispose_LocalValue_Binding |      0.25 |          459201.00 |          112800.00|
| Avalonia.Benchmarks.Styling.StyleAttachBenchmark.AttachTextBoxStyles             |      0.37 |             736.00 |             272.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 5, N |      0.41 |            3328.00 |            1352.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 5, N |      0.41 |            3328.00 |            1352.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 5, N |      0.41 |            3328.00 |            1352.00|
| Avalonia.Benchmarks.Traversal.VisualTreeTraversal.FindCommonVisualAncestor       |      0.42 |           12688.00 |            5312.00|
| Avalonia.Benchmarks.Base.AvaloniaObjectBenchmark.BindIntProperty                 |      0.42 |           15528.00 |            6560.00|
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Bind_Int_Property_Multiple_Pri |      0.43 |           39904.00 |           17216.00|
| Avalonia.Benchmarks.Styling.Style_ClassSelector.Apply_Toggle                     |      0.47 |             672.00 |             313.00|
| Avalonia.Benchmarks.Base.AvaloniaObject_SetValue.Set_Local_Values_With_Style_Val |      0.48 |          168000.00 |           80000.00|
| Avalonia.Benchmarks.Base.AvaloniaObject_Binding.Fire_LocalValue_Bindings_With_St |      0.51 |          182457.00 |           92488.00|
| Avalonia.Benchmarks.Base.StyledPropertyBenchmarks.Set_Coerced_Int_Property_Local |      0.51 |           13104.00 |            6744.00|
| Avalonia.Benchmarks.Styling.Style_ClassSelector.Apply_Detach                     |      0.52 |             589.00 |             305.00|
| Avalonia.Benchmarks.Styling.Style_ClassSelector.Apply                            |      0.60 |             508.00 |             305.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 1, N |      0.78 |            1624.00 |            1264.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 1, N |      0.78 |            1624.00 |            1264.00|
| Avalonia.Benchmarks.Styling.Style_Apply.Apply_Simple_Styles(MatchingStyles: 1, N |      0.78 |            1624.00 |            1264.00|
| Avalonia.Benchmarks.Data.BindingsBenchmark.UpdateTwoWayBinding_Via_Binding       |      0.78 |           34812.00 |           27103.00|
| Avalonia.Benchmarks.Themes.FluentBenchmark.RepeatButton                          |      0.84 |           38618.00 |           32363.00|
| Avalonia.Benchmarks.Styling.SelectorBenchmark.ClassSelector_NoMatch              |      0.86 |              56.00 |              48.00|
| Avalonia.Benchmarks.Styling.SelectorBenchmark.ClassSelector_Match                |      0.86 |              56.00 |              48.00|
| Avalonia.Benchmarks.Layout.ControlsBenchmark.CreateButton                        |      0.86 |           31010.00 |           26651.00|
| Avalonia.Benchmarks.Layout.ControlsBenchmark.CreateCalendar                      |      0.88 |         4240880.00 |         3717504.00|
| Avalonia.Benchmarks.Layout.ControlsBenchmark.CreateCalendarWithLoaded            |      0.88 |         4267224.00 |         3743848.00|
| Avalonia.Benchmarks.Base.AvaloniaObjectBenchmark.ClearAndSetIntProperty          |      0.88 |             208.00 |             184.00|
| Avalonia.Benchmarks.Layout.ControlsBenchmark.CreateTextBox                       |      0.90 |          235048.00 |          212624.00|
| Avalonia.Benchmarks.Base.AvaloniaObject_SetValue.SetValues                       |      0.91 |           88000.00 |           80000.00|
| Avalonia.Benchmarks.Themes.ThemeBenchmark.InitSimpleTheme(mode: Dark)            |      0.97 |          235682.00 |          228717.00|
| Avalonia.Benchmarks.Base.AvaloniaObject_Binding.Fire_LocalValue_Bindings         |      0.97 |           95472.00 |           92890.00|
| Avalonia.Benchmarks.Themes.ThemeBenchmark.InitFluentTheme(mode: Dark)            |      0.99 |          544204.00 |          536954.00|
| Avalonia.Benchmarks.Themes.ThemeBenchmark.InitFluentTheme(mode: Light)           |      0.99 |          543836.00 |          536707.00|

Of these, InitializeButton is only 26ns slower which is on the limit of what can be measured.

The nearly 19x improvement in reading inherited values should really make a difference in some applications.

Memory benchmarks have now been added. Most of increases in memory are because of the particular scenarios the benchmarks are testing and should be offset by the decreases in a real app.

Breaking Changes

Removes BeginBatchUpdate/EndBatchUpdate and replaces it with internal BeginStyling/EndStyling as the current batch update system introduced in 0.10.1 isn't zero cost, whereas the more limited BeginStyling/EndStyling is.

I want to re-think and maybe re-implement batch updates separately from this PR, at least for initialization, but if anyone is using them please let me know how/where.

Depends on #8571

MarchingCube and others added 30 commits June 5, 2022 17:34
Most (but not all) tests passing, all features mostly implemented exception coercion.
To remove `InheritanceFrame` - it was unneeded, we can just point to the nearest ancestor value store with inherited values.
With associated unit tests.
- Always evaluate the active state from current information, don't rely on subscriptions to fire as the current state may not be up-to-date
- Don't notify the `IStyleActivatorSink` of a change immediately on subscription
And use the concrete `ValueFrame` (renamed from `ValueFrameBase`) as this was the only implementation of the interface.
If a `StyleBase` has no activator, and its `Setter`s don't need to be separately instanced on the control, then we can share a `StyleInstance` between controls.

This causes failing tests because `TopLevel` applies styles in its constructor, but doesn't set `_styled` to true, meaning that styles get applied twice. This issue will need to be addressed separately.
And rename collection to indicate this.
@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0025609-beta. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json) [PRBUILDID]

Copy link
Collaborator

@MarchingCube MarchingCube left a comment

Choose a reason for hiding this comment

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

Also after main changes are done - it would be worth it and go over added classes and seal everything to help devirtualization.

src/Avalonia.Base/PropertyStore/ValueStore.cs Show resolved Hide resolved
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs Outdated Show resolved Hide resolved
src/Avalonia.Base/StyledElement.cs Show resolved Hide resolved
src/Avalonia.Base/StyledElement.cs Show resolved Hide resolved
src/Avalonia.Base/Styling/Setter.cs Show resolved Hide resolved
target.StyleApplied(instance);
instance.Start();
if (target is not AvaloniaObject ao)
throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Due to GH formatting this almost looks like as it happens all the time. Maybe adding braces here wouln't hurt.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what you mean here?

grokys and others added 2 commits November 8, 2022 16:42
Added `IValueEntry<T>` interface back in and use that if present to get the value from the entry.
@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0025918-beta. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json) [PRBUILDID]

Decreases the number of metadata lookups needed. Previously we were doing two lookups on construction now we're only doing one.
@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0025922-beta. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json) [PRBUILDID]

@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0025970-beta. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json) [PRBUILDID]

@grokys grokys mentioned this pull request Nov 10, 2022
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.

9 participants