Skip to content

Commit 4745066

Browse files
committed
Refactoring of DotvvmProperty value storage
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.
1 parent 2bbf954 commit 4745066

33 files changed

+2123
-517
lines changed

src/Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
<NoWarn>$(NoWarn);CS1591;CS1573</NoWarn>
2626
<Deterministic>true</Deterministic>
2727
<PreserveCompilationContext>true</PreserveCompilationContext>
28-
<DefaultTargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard2.1;net6.0;net472</DefaultTargetFrameworks>
29-
<DefaultTargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.1;net6.0</DefaultTargetFrameworks>
28+
<DefaultTargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net8.0;net6.0;netstandard2.1;net472</DefaultTargetFrameworks>
29+
<DefaultTargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net8.0;net6.0;netstandard2.1</DefaultTargetFrameworks>
3030
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
3131
<AutomaticallyUseReferenceAssemblyPackages>false</AutomaticallyUseReferenceAssemblyPackages>
3232
</PropertyGroup>

src/Framework/Framework/Binding/ActiveDotvvmProperty.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ namespace DotVVM.Framework.Binding
1313
/// <summary> An abstract DotvvmProperty which contains code to be executed when the assigned control is being rendered. </summary>
1414
public abstract class ActiveDotvvmProperty : DotvvmProperty
1515
{
16+
internal ActiveDotvvmProperty(string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
17+
{
18+
}
19+
1620
public abstract void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context, DotvvmControl control);
1721

1822

src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace DotVVM.Framework.Binding
1111
/// </summary>
1212
public class CompileTimeOnlyDotvvmProperty : DotvvmProperty
1313
{
14-
public CompileTimeOnlyDotvvmProperty()
14+
private CompileTimeOnlyDotvvmProperty(string name, Type declaringType) : base(name, declaringType, isValueInherited: false)
1515
{
1616
}
1717

@@ -37,7 +37,7 @@ public override bool IsSet(DotvvmBindableObject control, bool inherit = true)
3737
/// </summary>
3838
public static CompileTimeOnlyDotvvmProperty Register<TPropertyType, TDeclaringType>(string propertyName)
3939
{
40-
var property = new CompileTimeOnlyDotvvmProperty();
40+
var property = new CompileTimeOnlyDotvvmProperty(propertyName, typeof(TDeclaringType));
4141
return (CompileTimeOnlyDotvvmProperty)Register<TPropertyType, TDeclaringType>(propertyName, property: property);
4242
}
4343
}

src/Framework/Framework/Binding/DelegateActionProperty.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ namespace DotVVM.Framework.Binding
1313
/// <summary> DotvvmProperty which calls the function passed in the Register method, when the assigned control is being rendered. </summary>
1414
public sealed class DelegateActionProperty<TValue>: ActiveDotvvmProperty
1515
{
16-
private Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func;
16+
private readonly Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func;
1717

18-
public DelegateActionProperty(Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func)
18+
public DelegateActionProperty(Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func, string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
1919
{
2020
this.func = func;
2121
}
@@ -27,7 +27,8 @@ public override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestCon
2727

2828
public static DelegateActionProperty<TValue> Register<TDeclaringType>(string name, Action<IHtmlWriter, IDotvvmRequestContext, DotvvmProperty, DotvvmControl> func, [AllowNull] TValue defaultValue = default(TValue))
2929
{
30-
return (DelegateActionProperty<TValue>)DotvvmProperty.Register<TValue, TDeclaringType>(name, defaultValue, false, new DelegateActionProperty<TValue>(func));
30+
var property = new DelegateActionProperty<TValue>(func, name, typeof(TDeclaringType), isValueInherited: false);
31+
return (DelegateActionProperty<TValue>)DotvvmProperty.Register<TValue, TDeclaringType>(name, defaultValue, false, property);
3132
}
3233

3334
}

src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyG
105105
valueParameter
106106
)
107107
);
108-
109108
}
109+
110+
static readonly ConstructorInfo DotvvmPropertyIdConstructor = typeof(DotvvmPropertyId).GetConstructor(new [] { typeof(uint) }).NotNull();
111+
110112
public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyAccessors(Type type, DotvvmProperty property)
111113
{
112114
if (property is DotvvmPropertyAlias propertyAlias)
@@ -123,18 +125,26 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
123125
var valueParameter = Expression.Parameter(type, "value");
124126
var unwrappedType = type.UnwrapNullableType();
125127

128+
var defaultObj = TypeConversion.BoxToObject(Constant(property.DefaultValue));
129+
// try to access the readonly static field, as .NET can optimize that better than whatever Linq.Expression Constant compiles to
130+
var propertyExpr =
131+
property.AttributeProvider is FieldInfo field && field.IsStatic && field.IsInitOnly && field.GetValue(null) == property
132+
? Field(null, field)
133+
: (Expression)Constant(property);
134+
var propertyIdExpr = New(DotvvmPropertyIdConstructor, Constant(property.Id.Id, typeof(uint)));
135+
126136
var boxedValueParameter = TypeConversion.BoxToObject(valueParameter);
127137
var setValueRaw =
128138
canUseDirectAccess
129-
? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, Constant(property), boxedValueParameter)
130-
: Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, Constant(property), boxedValueParameter);
139+
? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj, boxedValueParameter)
140+
: Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, propertyExpr, boxedValueParameter);
131141

132142
if (typeof(IBinding).IsAssignableFrom(type))
133143
{
134144
var getValueRaw =
135145
canUseDirectAccess
136-
? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, Constant(property))
137-
: Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, Constant(property), Constant(property.IsValueInherited));
146+
? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj)
147+
: Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, propertyExpr, Constant(property.IsValueInherited));
138148
return (
139149
Lambda(
140150
Convert(getValueRaw, type),
@@ -173,11 +183,17 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
173183
Expression.Call(
174184
getValueOrBindingMethod,
175185
currentControlParameter,
176-
Constant(property)),
186+
canUseDirectAccess ? propertyIdExpr : propertyExpr,
187+
defaultObj),
177188
currentControlParameter
178189
),
179190
Expression.Lambda(
180-
Expression.Call(setValueOrBindingMethod, currentControlParameter, Expression.Constant(property), valueParameter),
191+
Expression.Call(
192+
setValueOrBindingMethod,
193+
currentControlParameter,
194+
canUseDirectAccess ? propertyIdExpr : propertyExpr,
195+
defaultObj,
196+
valueParameter),
181197
currentControlParameter, valueParameter
182198
)
183199
);
@@ -191,18 +207,18 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
191207
Expression getValue;
192208
if (canUseDirectAccess && unwrappedType.IsValueType)
193209
{
194-
getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), new Type[] { unwrappedType }, currentControlParameter, Constant(property));
195-
if (type.IsNullable())
210+
getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), new Type[] { unwrappedType }, currentControlParameter, propertyIdExpr, Constant(property.DefaultValue, type.MakeNullableType()));
211+
if (!type.IsNullable())
196212
getValue = Expression.Property(getValue, "Value");
197213
}
198214
else
199215
{
200-
getValue = Call(currentControlParameter, getValueMethod, Constant(property), Constant(property.IsValueInherited));
216+
getValue = Call(currentControlParameter, getValueMethod, propertyExpr, Constant(property.IsValueInherited));
201217
}
202218
return (
203219
Expression.Lambda(
204220
Expression.Convert(
205-
Expression.Call(currentControlParameter, getValueMethod, Expression.Constant(property), Expression.Constant(false)),
221+
Expression.Call(currentControlParameter, getValueMethod, propertyExpr, Expression.Constant(false)),
206222
type
207223
),
208224
currentControlParameter

src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,60 +12,60 @@ public partial class DotvvmCapabilityProperty
1212
{
1313
internal static class Helpers
1414
{
15-
public static ValueOrBinding<T>? GetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p)
15+
public static ValueOrBinding<T>? GetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
1616
{
1717
if (c.properties.TryGet(p, out var x))
1818
return ValueOrBinding<T>.FromBoxedValue(x);
1919
else return null;
2020
}
21-
public static ValueOrBinding<T> GetValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p)
21+
public static ValueOrBinding<T> GetValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
2222
{
2323
if (!c.properties.TryGet(p, out var x))
24-
x = p.DefaultValue;
24+
x = defaultValue;
2525
return ValueOrBinding<T>.FromBoxedValue(x);
2626
}
27-
public static ValueOrBinding<T>? GetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p)
27+
public static ValueOrBinding<T>? GetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
2828
{
2929
if (c.IsPropertySet(p))
3030
return ValueOrBinding<T>.FromBoxedValue(c.GetValue(p));
3131
else return null;
3232
}
33-
public static ValueOrBinding<T> GetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p)
33+
public static ValueOrBinding<T> GetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
3434
{
3535
return ValueOrBinding<T>.FromBoxedValue(c.GetValue(p));
3636
}
37-
public static void SetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T>? val)
37+
public static void SetOptionalValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding<T>? val)
3838
{
3939
if (val.HasValue)
4040
{
41-
SetValueOrBinding<T>(c, p, val.GetValueOrDefault());
41+
SetValueOrBinding<T>(c, p, defaultValue, val.GetValueOrDefault());
4242
}
4343
else
4444
{
4545
c.properties.Remove(p);
4646
}
4747
}
48-
public static void SetValueOrBinding<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T> val)
48+
public static void SetValueOrBinding<T>(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding<T> val)
4949
{
5050
var boxedVal = val.UnwrapToObject();
51-
SetValueDirect(c, p, boxedVal);
51+
SetValueDirect(c, p, defaultValue, boxedVal);
5252
}
53-
public static void SetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T>? val)
53+
public static void SetOptionalValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding<T>? val)
5454
{
5555
if (val.HasValue)
5656
{
57-
SetValueOrBindingSlow<T>(c, p, val.GetValueOrDefault());
57+
SetValueOrBindingSlow<T>(c, p, defaultValue, val.GetValueOrDefault());
5858
}
5959
else
6060
{
61-
c.SetValue(p, p.DefaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
61+
c.SetValue(p, defaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
6262
c.properties.Remove(p);
6363
}
6464
}
65-
public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding<T> val)
65+
public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding<T> val)
6666
{
6767
var boxedVal = val.UnwrapToObject();
68-
if (Object.Equals(boxedVal, p.DefaultValue) && c.IsPropertySet(p))
68+
if (Object.Equals(boxedVal, defaultValue) && c.IsPropertySet(p))
6969
{
7070
// setting to default value and the property is not set -> do nothing
7171
}
@@ -75,15 +75,15 @@ public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProper
7575
}
7676
}
7777

78-
public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmProperty p)
78+
public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmPropertyId p, object defaultValue)
7979
{
8080
if (c.properties.TryGet(p, out var x))
8181
{
8282
return x;
8383
}
84-
else return p.DefaultValue;
84+
else return defaultValue;
8585
}
86-
public static T? GetStructValueDirect<T>(DotvvmBindableObject c, DotvvmProperty p)
86+
public static T? GetStructValueDirect<T>(DotvvmBindableObject c, DotvvmPropertyId p, T? defaultValue)
8787
where T: struct
8888
{
8989
if (c.properties.TryGet(p, out var x))
@@ -94,11 +94,11 @@ public static void SetValueOrBindingSlow<T>(DotvvmBindableObject c, DotvvmProper
9494
return xValue;
9595
return (T?)c.EvalPropertyValue(p, x);
9696
}
97-
else return (T?)p.DefaultValue;
97+
else return defaultValue;
9898
}
99-
public static void SetValueDirect(DotvvmBindableObject c, DotvvmProperty p, object? value)
99+
public static void SetValueDirect(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, object? value)
100100
{
101-
if (Object.Equals(p.DefaultValue, value) && !c.properties.Contains(p))
101+
if (Object.Equals(defaultValue, value) && !c.properties.Contains(p))
102102
{
103103
// setting to default value and the property is not set -> do nothing
104104
}

src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,9 @@ private DotvvmCapabilityProperty(
4545
Type declaringType,
4646
ICustomAttributeProvider? attributeProvider,
4747
DotvvmCapabilityProperty? declaringCapability
48-
): base()
48+
): base(name ?? prefix + type.Name, declaringType, isValueInherited: false)
4949
{
50-
name ??= prefix + type.Name;
51-
52-
this.Name = name;
5350
this.PropertyType = type;
54-
this.DeclaringType = declaringType;
5551
this.Prefix = prefix;
5652
this.AddUsedInCapability(declaringCapability);
5753

@@ -63,14 +59,15 @@ private DotvvmCapabilityProperty(
6359

6460
AssertPropertyNotDefined(this, postContent: false);
6561

66-
var dotnetFieldName = ToPascalCase(name.Replace("-", "_").Replace(":", "_"));
62+
var dotnetFieldName = ToPascalCase(Name.Replace("-", "_").Replace(":", "_"));
6763
attributeProvider ??=
6864
declaringType.GetProperty(dotnetFieldName) ??
6965
declaringType.GetField(dotnetFieldName) ??
7066
(ICustomAttributeProvider?)declaringType.GetField(dotnetFieldName + "Property") ??
71-
throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{name}. Please declare a field or property named {dotnetFieldName}.");
67+
throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{Name}. Please declare a field or property named {dotnetFieldName}.");
7268

7369
DotvvmProperty.InitializeProperty(this, attributeProvider);
70+
this.MarkupOptions._mappingMode ??= MappingMode.Exclude;
7471
}
7572

7673
public override object GetValue(DotvvmBindableObject control, bool inherit = true) => Getter(control);
@@ -200,15 +197,8 @@ static DotvvmCapabilityProperty RegisterCapability(DotvvmCapabilityProperty prop
200197
{
201198
var declaringType = property.DeclaringType.NotNull();
202199
var capabilityType = property.PropertyType.NotNull();
203-
var name = property.Name.NotNull();
204200
AssertPropertyNotDefined(property);
205-
var attributes = new CustomAttributesProvider(
206-
new MarkupOptionsAttribute
207-
{
208-
MappingMode = MappingMode.Exclude
209-
}
210-
);
211-
DotvvmProperty.Register(name, capabilityType, declaringType, DBNull.Value, false, property, attributes);
201+
DotvvmProperty.Register(property);
212202
if (!capabilityRegistry.TryAdd((declaringType, capabilityType, property.Prefix), property))
213203
throw new($"unhandled naming conflict when registering capability {capabilityType}.");
214204
capabilityListRegistry.AddOrUpdate(

0 commit comments

Comments
 (0)