Identify DotvvmProperty with systematic 32-bit IDs#1867
Conversation
a2f0889 to
4745066
Compare
ddd5fa4 to
fcf2744
Compare
5a41705 to
72ec420
Compare
All DotvvmProperties are now assigned 32-bit ids, which can be used for more
efficient lookups and identifications. The ID is formatted to allow optimizing
certain common operations and make the assignment consistent even when
we initialize controls on multiple threads.
The ID format is (bit 0 is (id >> 31)&1, bit 31 is id&1)
* bit 0
- =1 - is property group
* bits 16-30: Identifies the property declaring type.
* bits 0-15: Identifies the property in the declaring type.
- =0 - any other DotvvmProperty
* bits 16-30: Identifies the property group
* bits 0-15: Identifies a string - the property group member
All IDs are assigned sequentially, with reserved blocks at the
start for the most important types which we might want to
adress directly in a compile-time constant.
IDs put a limit on the number of properties in a type (64k),
the number of property groups (32k), and the number of property
group members. All property groups share the same name dictionary,
which allows for significant memory savings, but it might
be limiting in obscure cases.
As property groups share the name-ID mapping, we do not to keep
the GroupedDotvvmProperty instances in memory after the compilation
is done. VirtualPropertyGroupDictionary will map strings directly
to the IDs and back.
Shorter unmanaged IDs allows for efficient lookups in unorganized
arrays using SIMD. 8 property IDs fit into a single vector register.
Since, controls with more than 8 properties are not common, we
can eliminate hashing with this "brute force".
We should evaluate whether it makes sense to keep the custom
small table--optimized hashtable. This patch keeps that in place.
The standard Dictionary`2 is also faster when indexed with integer
compared to a reference type.
Number of other places in the framework were adjusted to adress
properties directly using the IDs.
…rtual GetValue This is now frequently needed when working only with the ID instead of working with DotvvmProperty. Without this information, we pay not only for virtual dispatch, but also the (more expensive) cost of looking up the DotvvmProperty instance. Since most properties use plain DotvvmProperty and don't need the polymorphism, we now avoid this cost by using the last bit of the property ID to indicate whether the property can use direct access.
72ec420 to
caa736d
Compare
* Optimize relevant property accessed using the new compile-time constants * Optimize PropertyGroup.Clear * Cover DotvvmPropertyDictionary with more tests (and fix some bugs)
caa736d to
ddf5945
Compare
There was a problem hiding this comment.
Pull Request Overview
This PR implements systematic 32-bit IDs for DotvvmProperty to enable more efficient lookup and identification. All DotvvmProperties now receive sequential IDs with a 16-bit type ID + 16-bit property ID structure, allowing for optimized operations and consistent assignment even during multi-threaded control initialization.
Key changes include:
- Introduction of DotvvmPropertyId struct for efficient property identification using 32-bit IDs
- Replacement of perfect hashing with SIMD-optimized linear search for small property collections (8-16 elements)
- Implementation of copy-on-write semantics for control cloning to improve memory efficiency
Reviewed Changes
Copilot reviewed 52 out of 53 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Framework/Framework/Controls/PropertyImmutableHashtable.cs | Replaced with PropertyDictionaryImpl containing SIMD-optimized property lookup algorithms |
| src/Framework/Framework/Controls/DotvvmControlProperties.cs | Complete rewrite to support efficient array-based storage with copy-on-write semantics |
| src/Framework/Framework/scripts/generate-property-ids.mjs | New script to generate consistent property ID assignments |
| src/Framework/Framework/Controls/DotvvmBindableObject.cs | Updated to use new property ID system and optimized property access patterns |
| src/Framework/Framework/Controls/HtmlGenericControl.cs | Modified to use property IDs directly for performance-critical rendering paths |
Comments suppressed due to low confidence (1)
src/Framework/Framework/Controls/DotvvmControlProperties.cs:478
- [nitpick] The struct name 'PropertyGroupEnumerable' is ambiguous. Consider renaming to 'PropertyGroupCollection' or 'PropertyGroupView' to better indicate its purpose.
var slot = Impl.FindSlotOrFree8(keys, p, out var exists);
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
9c7ec4d to
50d3239
Compare
50d3239 to
065a219
Compare
|
I'm attaching benchmark results for this PR. TL;DR: it's faster by about 50% in targeted benchmarks. It's subset of the dotvvm-benchmarks suite run across .NET 8/9/10 with TieredPGO on/off. Improvement on the test plain-dotvvm page is smaller (~10%), since that benchmark does not mutate any DotvvmControl properties, and the old hashtable implementation served this workload well (it's not measured in this set) |
All DotvvmProperties now get assigned 32-bit IDs, which can be used for more efficient lookup and identification. The has a ID 16b + 16b structure to allow optimizing certain common operations and make the assignment consistent even when we initialize controls on multiple threads.
The ID format is (bit 0 is (id >> 31)&1, bit 31 is id&1)
All IDs are assigned sequentially, with reserved blocks at the start for the most important types which we might want to address directly in a compile-time constant.
IDs put a limit on the number of properties in a type (>= 32k), the number of property groups (32k), and the number of property group members (64k). All property groups share the same name dictionary, which allows for significant memory savings, but it might be limiting in obscure cases.
As property groups share the name-ID mapping, we do not to keep the
GroupedDotvvmPropertyinstances in memory after the compilation is done. VirtualPropertyGroupDictionary will map strings directly to the IDs and back, without going toGroupedDotvvmProperty.Shorter unmanaged IDs allows for efficient lookup in unorganized arrays using SIMD – 8 property IDs fit into a single vector register. Since controls with more than 8 properties are not common, the brute force will actually be faster than anything. The custom perfect-hashing algorithm is therefore removed. Possible states of the DotvvmControlProperties dictionary are:
keysandvaluesare bothnull(this is default struct state)keysandvaluesare 8 element arrays without any ordering guarantees (keys is 256 bits)keysandvaluesare 16 element arrays without any ordering guarantees (keys is 512 bits)keysis null,valuesisDictionary<DotvvmPropertyId, object?>. This is mainly fallback for the rare case someone puts this many properties onto a single control.Moreover, the dictionary remembers two booleans:
ownsValuesandownsKeyswhich are used for copy-on-write control cloning.valuesarray, while still copying keys by referenceownsValues = true, ownsKeys = falseownsValues = true(ownsKeys is untouched)ownsKeys = trueownsValues = trueNumber of other places in the framework were adjusted to address properties directly using the IDs to take advantage of the performance gains.