Refactor property writing logic in AcBinarySerializer

Split WriteObjectProperties into markerless and metadata variants for clarity and performance. Adjust method inlining attributes to favor hot path optimization. Comment out WritePropertyValue and some AcBinaryBenchmark variants to streamline code and benchmarks. Improves maintainability and serialization efficiency.
This commit is contained in:
Loretta 2026-03-09 22:06:58 +01:00
parent 76ce60b7f0
commit c84c26048c
3 changed files with 138 additions and 123 deletions

View File

@ -224,9 +224,9 @@ public static class Program
// AcBinary variants // AcBinary variants
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode),
new AcBinaryBenchmark(testData.Order, binaryFastModeNoSgenOption, SerializerAcBinaryFastNoSgen), //new AcBinaryBenchmark(testData.Order, binaryFastModeNoSgenOption, SerializerAcBinaryFastNoSgen),
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault),
new AcBinaryBenchmark(testData.Order, binaryDefaultNoSgenOption, SerializerAcBinaryDefaultNoSgen), //new AcBinaryBenchmark(testData.Order, binaryDefaultNoSgenOption, SerializerAcBinaryDefaultNoSgen),
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef),
new AcBinaryBenchmark(testData.Order, binaryNoInternOption, SerializerAcBinaryNoIntern), new AcBinaryBenchmark(testData.Order, binaryNoInternOption, SerializerAcBinaryNoIntern),

View File

@ -723,6 +723,7 @@ public static partial class AcBinarySerializer
WriteBytes(utf8Name); WriteBytes(utf8Name);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteStringUtf8Internal(string value) private void WriteStringUtf8Internal(string value)
{ {
var byteCount = Utf8NoBom.GetByteCount(value); var byteCount = Utf8NoBom.GetByteCount(value);
@ -930,7 +931,7 @@ public static partial class AcBinarySerializer
/// Cached type: ObjectWithTypeIndexRefFirst (71) + typeIndex + refCacheIndex /// Cached type: ObjectWithTypeIndexRefFirst (71) + typeIndex + refCacheIndex
/// </para> /// </para>
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] //[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePolymorphicPrefix(Type runtimeType, int cachedObjectCacheIndex = -1) public void WritePolymorphicPrefix(Type runtimeType, int cachedObjectCacheIndex = -1)
{ {
var rtWrapper = GetWrapper(runtimeType); var rtWrapper = GetWrapper(runtimeType);
@ -991,7 +992,7 @@ public static partial class AcBinarySerializer
/// Első előfordulás: [propNameHash (4b)][propCount (VarUInt)][hash0 (4b)][hash1 (4b)]... /// Első előfordulás: [propNameHash (4b)][propCount (VarUInt)][hash0 (4b)][hash1 (4b)]...
/// Ismételt: [propNameHash (4b)] /// Ismételt: [propNameHash (4b)]
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] //[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInlineMetadata(BinarySerializeTypeMetadata metadata, bool isFirstOccurrence) public void WriteInlineMetadata(BinarySerializeTypeMetadata metadata, bool isFirstOccurrence)
{ {
WriteRaw(metadata.PropNameHash); WriteRaw(metadata.PropNameHash);

View File

@ -591,7 +591,7 @@ public static partial class AcBinarySerializer
#endregion #endregion
#region Value Writing #region Value Writing
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteValue<TOutput>(object? value, Type type, BinarySerializationContext<TOutput> context, int depth) private static void WriteValue<TOutput>(object? value, Type type, BinarySerializationContext<TOutput> context, int depth)
where TOutput : struct, IBinaryOutputBase where TOutput : struct, IBinaryOutputBase
{ {
@ -1114,7 +1114,7 @@ public static partial class AcBinarySerializer
/// <summary> /// <summary>
/// String intern 2nd occurrence — cold path, just writes reference index. /// String intern 2nd occurrence — cold path, just writes reference index.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] //[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteStringInternRef<TOutput>(BinarySerializationContext<TOutput> context, int cacheMapIndex) private static void WriteStringInternRef<TOutput>(BinarySerializationContext<TOutput> context, int cacheMapIndex)
where TOutput : struct, IBinaryOutputBase where TOutput : struct, IBinaryOutputBase
{ {
@ -1125,7 +1125,7 @@ public static partial class AcBinarySerializer
/// <summary> /// <summary>
/// Object ref 2nd occurrence — cold path, just writes reference index. /// Object ref 2nd occurrence — cold path, just writes reference index.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteObjectRef<TOutput>(BinarySerializationContext<TOutput> context, int cacheMapIndex) private static void WriteObjectRef<TOutput>(BinarySerializationContext<TOutput> context, int cacheMapIndex)
where TOutput : struct, IBinaryOutputBase where TOutput : struct, IBinaryOutputBase
{ {
@ -1177,14 +1177,14 @@ public static partial class AcBinarySerializer
context.WriteByte(BinaryTypeCode.Object); context.WriteByte(BinaryTypeCode.Object);
} }
WriteObjectProperties(value, metadata, wrapper, context, depth, useMetaForType); WriteObjectProperties(value, wrapper, context, depth, useMetaForType);
} }
/// <summary> /// <summary>
/// WriteObject variant with reference handling, no metadata. /// WriteObject variant with reference handling, no metadata.
/// Cold path: only IId types with ref tracking enabled. /// Cold path: only IId types with ref tracking enabled.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteObjectWithRefHandling<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth) private static void WriteObjectWithRefHandling<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
where TOutput : struct, IBinaryOutputBase where TOutput : struct, IBinaryOutputBase
{ {
@ -1223,14 +1223,14 @@ public static partial class AcBinarySerializer
context.WriteByte(BinaryTypeCode.Object); context.WriteByte(BinaryTypeCode.Object);
} }
WriteObjectProperties(value, wrapper.Metadata, wrapper, context, depth, useMetaForType: false); WriteObjectProperties(value, wrapper, context, depth, useMetaForType: false);
} }
/// <summary> /// <summary>
/// WriteObject variant with reference handling + metadata. /// WriteObject variant with reference handling + metadata.
/// Cold path: IId types with ref tracking + UseMetadata enabled. /// Cold path: IId types with ref tracking + UseMetadata enabled.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] //[MethodImpl(MethodImplOptions.NoInlining)]
private static void WriteObjectWithRefHandlingMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth) private static void WriteObjectWithRefHandlingMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
where TOutput : struct, IBinaryOutputBase where TOutput : struct, IBinaryOutputBase
{ {
@ -1264,19 +1264,17 @@ public static partial class AcBinarySerializer
} }
context.WriteInlineMetadata(wrapper.Metadata, isFirstMetadataOccurrence); context.WriteInlineMetadata(wrapper.Metadata, isFirstMetadataOccurrence);
WriteObjectProperties(value, wrapper.Metadata, wrapper, context, depth, useMetaForType: true); WriteObjectProperties(value, wrapper, context, depth, useMetaForType: true);
} }
/// <summary> /// <summary>
/// Shared property writing loop — used by WriteObject, WriteObjectWithRefHandling, WriteObjectPolymorphic. /// Shared property writing loop — used by WriteObject, WriteObjectWithRefHandling, WriteObjectPolymorphic.
/// </summary> /// </summary>
private static void WriteObjectProperties<TOutput>(object value, BinarySerializeTypeMetadata metadata, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth, bool useMetaForType) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteObjectProperties<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth, bool useMetaForType)
where TOutput : struct, IBinaryOutputBase where TOutput : struct, IBinaryOutputBase
{ {
var nextDepth = depth + 1; var nextDepth = depth + 1;
var properties = metadata.Properties;
var propCount = properties.Length;
var hasPropertyFilter = context.HasPropertyFilter;
if (context.UseGeneratedCode) if (context.UseGeneratedCode)
{ {
@ -1290,6 +1288,41 @@ public static partial class AcBinarySerializer
if (!useMetaForType) if (!useMetaForType)
{ {
WritePropertiesMarkerless(value, wrapper, context, nextDepth);
}
else
{
WritePropertiesWithMeta(value, wrapper, context, nextDepth);
}
}
private static void WritePropertiesWithMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int nextDepth) where TOutput : struct, IBinaryOutputBase
{
var properties = wrapper.Metadata.Properties;
var propCount = properties.Length;
var hasPropertyFilter = context.HasPropertyFilter;
for (var i = 0; i < propCount; i++)
{
var prop = properties[i];
if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
{
context.WriteByte(BinaryTypeCode.PropertySkip);
continue;
}
WritePropertyOrSkip(value, prop, wrapper, context, nextDepth);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WritePropertiesMarkerless<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int nextDepth) where TOutput : struct, IBinaryOutputBase
{
var properties = wrapper.Metadata.Properties;
var propCount = properties.Length;
var hasPropertyFilter = context.HasPropertyFilter;
for (var i = 0; i < propCount; i++) for (var i = 0; i < propCount; i++)
{ {
var prop = properties[i]; var prop = properties[i];
@ -1308,32 +1341,13 @@ public static partial class AcBinarySerializer
} }
} }
} }
else
{
for (var i = 0; i < propCount; i++)
{
var prop = properties[i];
if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
{
context.WriteByte(BinaryTypeCode.PropertySkip);
continue;
}
WritePropertyOrSkip(value, prop, wrapper, context, nextDepth);
}
}
}
/// <summary> /// <summary>
/// Polymorphic marker writing — extracted from WriteObject to keep hot path small. /// Polymorphic marker writing — extracted from WriteObject to keep hot path small.
/// Cold path: polymorphism is rare, NoInlining call overhead acceptable. /// Cold path: polymorphism is rare, NoInlining call overhead acceptable.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static void WritePolymorphicMarker<TOutput>( private static void WritePolymorphicMarker<TOutput>(BinarySerializationContext<TOutput> context, Type polyRuntimeType, int cachedObjectCacheIndex)
BinarySerializationContext<TOutput> context,
Type polyRuntimeType,
int cachedObjectCacheIndex)
where TOutput : struct, IBinaryOutputBase where TOutput : struct, IBinaryOutputBase
{ {
if (cachedObjectCacheIndex >= 0) if (cachedObjectCacheIndex >= 0)
@ -1391,7 +1405,7 @@ public static partial class AcBinarySerializer
// Poly marker (handles combined poly+ref) // Poly marker (handles combined poly+ref)
WritePolymorphicMarker(context, polyRuntimeType, cachedObjectCacheIndex); WritePolymorphicMarker(context, polyRuntimeType, cachedObjectCacheIndex);
WriteObjectProperties(value, metadata, wrapper, context, depth, false); WriteObjectProperties(value, wrapper, context, depth, false);
} }
/// <summary> /// <summary>
@ -1443,83 +1457,83 @@ public static partial class AcBinarySerializer
/// <summary> /// <summary>
/// Writes a property value using typed getters to avoid boxing. /// Writes a property value using typed getters to avoid boxing.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] //[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WritePropertyValue<TOutput>(object obj, BinaryPropertyAccessor prop, BinarySerializationContext<TOutput> context, int depth) //private static void WritePropertyValue<TOutput>(object obj, BinaryPropertyAccessor prop, BinarySerializationContext<TOutput> context, int depth)
where TOutput : struct, IBinaryOutputBase // where TOutput : struct, IBinaryOutputBase
{ //{
switch (prop.AccessorType) // switch (prop.AccessorType)
{ // {
case PropertyAccessorType.Int32: // case PropertyAccessorType.Int32:
WriteInt32(prop.GetInt32(obj), context); // WriteInt32(prop.GetInt32(obj), context);
return; // return;
case PropertyAccessorType.Int64: // case PropertyAccessorType.Int64:
WriteInt64(prop.GetInt64(obj), context); // WriteInt64(prop.GetInt64(obj), context);
return; // return;
case PropertyAccessorType.Boolean: // case PropertyAccessorType.Boolean:
context.WriteByte(prop.GetBoolean(obj) ? BinaryTypeCode.True : BinaryTypeCode.False); // context.WriteByte(prop.GetBoolean(obj) ? BinaryTypeCode.True : BinaryTypeCode.False);
return; // return;
case PropertyAccessorType.Double: // case PropertyAccessorType.Double:
WriteFloat64Unsafe(prop.GetDouble(obj), context); // WriteFloat64Unsafe(prop.GetDouble(obj), context);
return; // return;
case PropertyAccessorType.Single: // case PropertyAccessorType.Single:
WriteFloat32Unsafe(prop.GetSingle(obj), context); // WriteFloat32Unsafe(prop.GetSingle(obj), context);
return; // return;
case PropertyAccessorType.Decimal: // case PropertyAccessorType.Decimal:
WriteDecimalUnsafe(prop.GetDecimal(obj), context); // WriteDecimalUnsafe(prop.GetDecimal(obj), context);
return; // return;
case PropertyAccessorType.DateTime: // case PropertyAccessorType.DateTime:
WriteDateTimeUnsafe(prop.GetDateTime(obj), context); // WriteDateTimeUnsafe(prop.GetDateTime(obj), context);
return; // return;
case PropertyAccessorType.Byte: // case PropertyAccessorType.Byte:
context.WriteByte(BinaryTypeCode.UInt8); // context.WriteByte(BinaryTypeCode.UInt8);
context.WriteByte(prop.GetByte(obj)); // context.WriteByte(prop.GetByte(obj));
return; // return;
case PropertyAccessorType.Int16: // case PropertyAccessorType.Int16:
WriteInt16Unsafe(prop.GetInt16(obj), context); // WriteInt16Unsafe(prop.GetInt16(obj), context);
return; // return;
case PropertyAccessorType.UInt16: // case PropertyAccessorType.UInt16:
WriteUInt16Unsafe(prop.GetUInt16(obj), context); // WriteUInt16Unsafe(prop.GetUInt16(obj), context);
return; // return;
case PropertyAccessorType.UInt32: // case PropertyAccessorType.UInt32:
WriteUInt32(prop.GetUInt32(obj), context); // WriteUInt32(prop.GetUInt32(obj), context);
return; // return;
case PropertyAccessorType.UInt64: // case PropertyAccessorType.UInt64:
WriteUInt64(prop.GetUInt64(obj), context); // WriteUInt64(prop.GetUInt64(obj), context);
return; // return;
case PropertyAccessorType.Guid: // case PropertyAccessorType.Guid:
WriteGuidUnsafe(prop.GetGuid(obj), context); // WriteGuidUnsafe(prop.GetGuid(obj), context);
return; // return;
case PropertyAccessorType.Enum: // case PropertyAccessorType.Enum:
var enumValue = prop.GetEnumAsInt32(obj); // var enumValue = prop.GetEnumAsInt32(obj);
if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny)) // if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny))
{ // {
context.WriteByte(BinaryTypeCode.Enum); // context.WriteByte(BinaryTypeCode.Enum);
context.WriteByte(tiny); // context.WriteByte(tiny);
} // }
else // else
{ // {
context.WriteByte(BinaryTypeCode.Enum); // context.WriteByte(BinaryTypeCode.Enum);
context.WriteByte(BinaryTypeCode.Int32); // context.WriteByte(BinaryTypeCode.Int32);
context.WriteVarInt(enumValue); // context.WriteVarInt(enumValue);
} // }
return; // return;
case PropertyAccessorType.String: // case PropertyAccessorType.String:
{ // {
// Fast path: typed getter, no boxing, no Type.GetTypeCode() call // // Fast path: typed getter, no boxing, no Type.GetTypeCode() call
var strValue = prop.GetString(obj); // var strValue = prop.GetString(obj);
if (strValue != null) // if (strValue != null)
WriteString(strValue, context); // WriteString(strValue, context);
else // else
context.WriteByte(BinaryTypeCode.Null); // context.WriteByte(BinaryTypeCode.Null);
return; // return;
} // }
default: // default:
// Fallback to object getter for reference types // // Fallback to object getter for reference types
var value = prop.GetValue(obj); // var value = prop.GetValue(obj);
WriteValue(value, prop.PropertyType, context, depth); // WriteValue(value, prop.PropertyType, context, depth);
return; // return;
} // }
} //}
/// <summary> /// <summary>
/// Writes a property value OR a skip marker if the value is default/null. /// Writes a property value OR a skip marker if the value is default/null.