Reduce DynamicMethod use in Serv3

Data definitions created individual read/write methods for every single field that can be serialized. This was extremely inefficient and likely caused lots of overhead.

These methods are necessary because, in some cases, we can't directly use expression trees to write fields. But... we can still do it most of the time! So now for most data fields we can avoid a DynamicMethod entirely.
This commit is contained in:
PJB3005
2025-01-16 15:09:47 +01:00
parent 448e8b0c2c
commit eaaa70437a
5 changed files with 48 additions and 9 deletions

View File

@@ -47,7 +47,7 @@ END TEMPLATE-->
### Other
*None yet*
* Reduced amount of `DynamicMethod`s used by serialization system. This should improve performance somewhat.
### Internal

View File

@@ -220,7 +220,7 @@ internal partial class UserInterfaceManager
continue;
var typeDict = _assignerRegistry.GetOrNew(fieldInfo.FieldType);
var assigner = (InternalReflectionUtils.AssignField<UIController, object?>)InternalReflectionUtils.EmitFieldAssigner<UIController>(backingField, true);
var assigner = (InternalReflectionUtils.AssignField<UIController, object?>)InternalReflectionUtils.EmitFieldAssigner(typeof(UIController), backingField, true);
typeDict.Add(controllerType, assigner);
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Robust.Shared.Network;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -476,15 +477,32 @@ namespace Robust.Shared.Serialization.Manager.Definition
private Expression AssignIfNotDefaultExpression(int i, Expression obj, Expression value)
{
var assigner = FieldAssigners[i];
Expression assignerExpr;
if (assigner is FieldInfo fieldInfo)
assignerExpr = Expression.Assign(Expression.Field(obj, fieldInfo), value);
else if (assigner is MethodInfo methodInfo)
assignerExpr = Expression.Call(obj, methodInfo, value);
else
assignerExpr = Expression.Invoke(Expression.Constant(assigner), obj, value);
return Expression.IfThen(
Expression.Not(ExpressionUtils.EqualExpression(
Expression.Constant(DefaultValues[i], BaseFieldDefinitions[i].FieldType), value)),
Expression.Invoke(Expression.Constant(FieldAssigners[i]), obj, value));
assignerExpr);
}
private Expression AccessExpression(int i, Expression obj)
{
return Expression.Invoke(Expression.Constant(FieldAccessors[i]), obj);
var accessor = FieldAccessors[i];
if (accessor is FieldInfo fieldInfo)
return Expression.Field(obj, fieldInfo);
if (accessor is MethodInfo methodInfo)
return Expression.Call(obj, methodInfo);
return Expression.Invoke(Expression.Constant(accessor), obj);
}
private Expression IsDefault(int i, Expression left, FieldDefinition fieldDefinition)

View File

@@ -95,7 +95,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
for (var i = 0; i < BaseFieldDefinitions.Length; i++)
{
var fieldDefinition = BaseFieldDefinitions[i];
fieldAssigners[i] = InternalReflectionUtils.EmitFieldAssigner<T>(fieldDefinition.BackingField);
fieldAssigners[i] = InternalReflectionUtils.EmitFieldAssigner(typeof(T), fieldDefinition.BackingField);
fieldAccessors[i] = InternalReflectionUtils.EmitFieldAccessor(typeof(T), fieldDefinition);
if (fieldDefinition.Attribute.CustomTypeSerializer != null)

View File

@@ -39,6 +39,12 @@ internal static class InternalReflectionUtils
internal static object EmitFieldAccessor(Type obj, FieldDefinition fieldDefinition)
{
if (fieldDefinition.BackingField is SpecificFieldInfo fieldInfo)
return fieldInfo.FieldInfo;
if (fieldDefinition.BackingField is SpecificPropertyInfo propertyInfo)
return propertyInfo.PropertyInfo.GetGetMethod(true) ?? throw new InvalidOperationException("Property has no getter");
var method = new DynamicMethod(
"AccessField",
fieldDefinition.BackingField.FieldType,
@@ -71,14 +77,29 @@ internal static class InternalReflectionUtils
return method.CreateDelegate(typeof(AccessField<,>).MakeGenericType(obj, fieldDefinition.BackingField.FieldType));
}
internal static object EmitFieldAssigner<TObj>(AbstractFieldInfo backingField, bool boxing = false)
internal static object EmitFieldAssigner(Type objType, AbstractFieldInfo backingField, bool boxing = false)
{
if (!boxing)
{
if (backingField is SpecificFieldInfo { FieldInfo.IsInitOnly: false } fieldInfo)
return fieldInfo.FieldInfo;
if (backingField is SpecificPropertyInfo propertyInfo)
{
if (propertyInfo.TryGetBackingField(out var propertyBackingField) && !propertyBackingField.FieldInfo.IsInitOnly)
return propertyBackingField.FieldInfo;
if (propertyInfo.PropertyInfo.GetSetMethod(true) is { } setMethod)
return setMethod;
}
}
var fieldType = backingField.FieldType;
var method = new DynamicMethod(
"AssignField",
typeof(void),
new[] {typeof(TObj).MakeByRefType(), boxing ? typeof(object) : fieldType},
new[] {objType.MakeByRefType(), boxing ? typeof(object) : fieldType},
true);
method.DefineParameter(1, ParameterAttributes.Out, "target");
@@ -88,7 +109,7 @@ internal static class InternalReflectionUtils
generator.Emit(OpCodes.Ldarg_0);
if(!typeof(TObj).IsValueType)
if(!objType.IsValueType)
generator.Emit(OpCodes.Ldind_Ref);
generator.Emit(OpCodes.Ldarg_1);
@@ -102,6 +123,6 @@ internal static class InternalReflectionUtils
generator.Emit(OpCodes.Ret);
return method.CreateDelegate(typeof(AssignField<,>).MakeGenericType(typeof(TObj), boxing ? typeof(object) : fieldType));
return method.CreateDelegate(typeof(AssignField<,>).MakeGenericType(objType, boxing ? typeof(object) : fieldType));
}
}