Skip to content

Commit 31b1fb2

Browse files
committed
Implement precompiled serializers for complex types to improve serialization performance
1 parent 8347f8c commit 31b1fb2

1 file changed

Lines changed: 63 additions & 23 deletions

File tree

CoreRemoting/Serialization/NeoBinary/NeoBinarySerializer.cs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ public class NeoBinarySerializer
2828
private readonly ConcurrentDictionary<Type, string> _typeNameCache = new();
2929
private readonly ConcurrentDictionary<string, Type> _resolvedTypeCache = new();
3030
private readonly ConcurrentDictionary<FieldInfo, Func<object, object>> _getterCache = new();
31-
31+
3232
// Performance optimization: compiled field setter delegates
3333
private readonly ConcurrentDictionary<FieldInfo, Action<object, object>> _setterCache = new();
34-
34+
35+
// Performance optimization: precompiled serializers for complex types
36+
private readonly ConcurrentDictionary<Type, Action<object, BinaryWriter, HashSet<object>, Dictionary<object, int>>> _compiledSerializers = new();
37+
3538
// String pooling for frequently used strings
3639
private readonly ConcurrentDictionary<string, string> _stringPool = new();
3740

@@ -924,25 +927,53 @@ private void SerializeComplexObject(object obj, BinaryWriter writer, HashSet<obj
924927
Dictionary<object, int> objectMap)
925928
{
926929
var type = obj.GetType();
930+
931+
if (_compiledSerializers.TryGetValue(type, out var compiledSerializer))
932+
{
933+
compiledSerializer(obj, writer, serializedObjects, objectMap);
934+
}
935+
else
936+
{
937+
var serializer = CreateCompiledSerializer(type);
938+
_compiledSerializers[type] = serializer;
939+
serializer(obj, writer, serializedObjects, objectMap);
940+
}
941+
}
942+
943+
private Action<object, BinaryWriter, HashSet<object>, Dictionary<object, int>> CreateCompiledSerializer(Type type)
944+
{
927945
var fields = GetAllFieldsInHierarchy(type);
928946

929-
writer.Write(fields.Count);
947+
var objParam = Expression.Parameter(typeof(object), "obj");
948+
var writerParam = Expression.Parameter(typeof(BinaryWriter), "writer");
949+
var serializedObjectsParam = Expression.Parameter(typeof(HashSet<object>), "serializedObjects");
950+
var objectMapParam = Expression.Parameter(typeof(Dictionary<object, int>), "objectMap");
951+
952+
var thisParam = Expression.Constant(this);
953+
954+
var statements = new List<Expression>();
955+
956+
// Write field count
957+
statements.Add(Expression.Call(writerParam, typeof(BinaryWriter).GetMethod("Write", new[] { typeof(int) })!, Expression.Constant(fields.Count)));
930958

931959
foreach (var field in fields)
932960
{
961+
// Write field name
933962
var fieldName = _stringPool.GetOrAdd(field.Name, n => n);
934-
writer.Write(fieldName);
935-
var getter = _getterCache.GetOrAdd(field, f =>
936-
{
937-
var objParam = Expression.Parameter(typeof(object), "obj");
938-
var castObj = Expression.Convert(objParam, f.DeclaringType);
939-
var fieldExpr = Expression.Field(castObj, f);
940-
var convertExpr = Expression.Convert(fieldExpr, typeof(object));
941-
return Expression.Lambda<Func<object, object>>(convertExpr, objParam).Compile();
942-
});
943-
var value = getter(obj);
944-
SerializeObject(value, writer, serializedObjects, objectMap);
963+
statements.Add(Expression.Call(writerParam, typeof(BinaryWriter).GetMethod("Write", new[] { typeof(string) })!, Expression.Constant(fieldName)));
964+
965+
// Get field value
966+
var castObj = Expression.Convert(objParam, field.DeclaringType!);
967+
var fieldExpr = Expression.Field(castObj, field);
968+
var valueExpr = Expression.Convert(fieldExpr, typeof(object));
969+
970+
// Call SerializeObject
971+
statements.Add(Expression.Call(thisParam, typeof(NeoBinarySerializer).GetMethod("SerializeObject", BindingFlags.NonPublic | BindingFlags.Instance)!, valueExpr, writerParam, serializedObjectsParam, objectMapParam));
945972
}
973+
974+
var block = Expression.Block(statements);
975+
var lambda = Expression.Lambda<Action<object, BinaryWriter, HashSet<object>, Dictionary<object, int>>>(block, objParam, writerParam, serializedObjectsParam, objectMapParam);
976+
return lambda.Compile();
946977
}
947978

948979
private object DeserializeComplexObject(Type type, BinaryReader reader,
@@ -972,45 +1003,54 @@ private object DeserializeComplexObject(Type type, BinaryReader reader,
9721003
{
9731004
continue;
9741005
}
975-
1006+
9761007
// Use reflection to set the field value on the boxed struct
9771008
field.SetValue(obj, value);
9781009
}
9791010
}
980-
1011+
9811012
return obj;
9821013
}
9831014
else
9841015
{
985-
// Regular reference type handling
1016+
// Regular reference type handling with optimized field lookup
1017+
var fields = _fieldCache.GetOrAdd(type, t => GetAllFieldsInHierarchy(t).ToArray());
1018+
var fieldIndex = 0;
1019+
9861020
for (int i = 0; i < fieldCount; i++)
9871021
{
9881022
var fieldName = reader.ReadString();
9891023
var value = DeserializeObject(reader, deserializedObjects);
9901024

991-
var field = GetFieldInHierarchy(type, fieldName);
992-
if (field != null)
1025+
// Find the field by name from cached fields
1026+
FieldInfo? field = null;
1027+
while (fieldIndex < fields.Length && (field = fields[fieldIndex]).Name != fieldName)
1028+
{
1029+
fieldIndex++;
1030+
}
1031+
1032+
if (field != null && field.Name == fieldName)
9931033
{
9941034
// Use cached setter for better performance, but fallback to reflection for read-only fields
9951035
if (field.IsInitOnly || field.IsLiteral)
9961036
{
9971037
// Skip read-only fields (they should be handled by constructor)
9981038
continue;
9991039
}
1000-
1040+
10011041
var setter = _setterCache.GetOrAdd(field, f =>
10021042
{
10031043
var objParam = Expression.Parameter(typeof(object), "obj");
10041044
var valueParam = Expression.Parameter(typeof(object), "value");
1005-
1045+
10061046
var castObj = Expression.Convert(objParam, type);
10071047
var castValue = Expression.Convert(valueParam, f.FieldType);
10081048
var fieldAccess = Expression.Field(castObj, f);
10091049
var assign = Expression.Assign(fieldAccess, castValue);
1010-
1050+
10111051
return Expression.Lambda<Action<object, object>>(assign, objParam, valueParam).Compile();
10121052
});
1013-
1053+
10141054
setter(obj, value);
10151055
}
10161056
}

0 commit comments

Comments
 (0)