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:
parent
76ce60b7f0
commit
c84c26048c
|
|
@ -224,9 +224,9 @@ public static class Program
|
|||
|
||||
// AcBinary variants
|
||||
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, binaryDefaultNoSgenOption, SerializerAcBinaryDefaultNoSgen),
|
||||
//new AcBinaryBenchmark(testData.Order, binaryDefaultNoSgenOption, SerializerAcBinaryDefaultNoSgen),
|
||||
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef),
|
||||
new AcBinaryBenchmark(testData.Order, binaryNoInternOption, SerializerAcBinaryNoIntern),
|
||||
|
||||
|
|
|
|||
|
|
@ -723,6 +723,7 @@ public static partial class AcBinarySerializer
|
|||
WriteBytes(utf8Name);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WriteStringUtf8Internal(string value)
|
||||
{
|
||||
var byteCount = Utf8NoBom.GetByteCount(value);
|
||||
|
|
@ -930,7 +931,7 @@ public static partial class AcBinarySerializer
|
|||
/// Cached type: ObjectWithTypeIndexRefFirst (71) + typeIndex + refCacheIndex
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WritePolymorphicPrefix(Type runtimeType, int cachedObjectCacheIndex = -1)
|
||||
{
|
||||
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)]...
|
||||
/// Ismételt: [propNameHash (4b)]
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteInlineMetadata(BinarySerializeTypeMetadata metadata, bool isFirstOccurrence)
|
||||
{
|
||||
WriteRaw(metadata.PropNameHash);
|
||||
|
|
|
|||
|
|
@ -591,7 +591,7 @@ public static partial class AcBinarySerializer
|
|||
#endregion
|
||||
|
||||
#region Value Writing
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteValue<TOutput>(object? value, Type type, BinarySerializationContext<TOutput> context, int depth)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
|
|
@ -1114,7 +1114,7 @@ public static partial class AcBinarySerializer
|
|||
/// <summary>
|
||||
/// String intern 2nd occurrence — cold path, just writes reference index.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteStringInternRef<TOutput>(BinarySerializationContext<TOutput> context, int cacheMapIndex)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
|
|
@ -1125,7 +1125,7 @@ public static partial class AcBinarySerializer
|
|||
/// <summary>
|
||||
/// Object ref 2nd occurrence — cold path, just writes reference index.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteObjectRef<TOutput>(BinarySerializationContext<TOutput> context, int cacheMapIndex)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
|
|
@ -1177,14 +1177,14 @@ public static partial class AcBinarySerializer
|
|||
context.WriteByte(BinaryTypeCode.Object);
|
||||
}
|
||||
|
||||
WriteObjectProperties(value, metadata, wrapper, context, depth, useMetaForType);
|
||||
WriteObjectProperties(value, wrapper, context, depth, useMetaForType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WriteObject variant with reference handling, no metadata.
|
||||
/// Cold path: only IId types with ref tracking enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WriteObjectWithRefHandling<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
|
|
@ -1223,14 +1223,14 @@ public static partial class AcBinarySerializer
|
|||
context.WriteByte(BinaryTypeCode.Object);
|
||||
}
|
||||
|
||||
WriteObjectProperties(value, wrapper.Metadata, wrapper, context, depth, useMetaForType: false);
|
||||
WriteObjectProperties(value, wrapper, context, depth, useMetaForType: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WriteObject variant with reference handling + metadata.
|
||||
/// Cold path: IId types with ref tracking + UseMetadata enabled.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
//[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void WriteObjectWithRefHandlingMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
|
|
@ -1264,20 +1264,18 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
context.WriteInlineMetadata(wrapper.Metadata, isFirstMetadataOccurrence);
|
||||
|
||||
WriteObjectProperties(value, wrapper.Metadata, wrapper, context, depth, useMetaForType: true);
|
||||
WriteObjectProperties(value, wrapper, context, depth, useMetaForType: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared property writing loop — used by WriteObject, WriteObjectWithRefHandling, WriteObjectPolymorphic.
|
||||
/// </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
|
||||
{
|
||||
var nextDepth = depth + 1;
|
||||
var properties = metadata.Properties;
|
||||
var propCount = properties.Length;
|
||||
var hasPropertyFilter = context.HasPropertyFilter;
|
||||
|
||||
|
||||
if (context.UseGeneratedCode)
|
||||
{
|
||||
var generatedWriter = wrapper.GeneratedWriter;
|
||||
|
|
@ -1290,36 +1288,55 @@ public static partial class AcBinarySerializer
|
|||
|
||||
if (!useMetaForType)
|
||||
{
|
||||
for (var i = 0; i < propCount; i++)
|
||||
{
|
||||
var prop = properties[i];
|
||||
|
||||
if (prop.ExpectedTypeCode.HasValue)
|
||||
{
|
||||
WritePropertyMarkerless(value, prop, context);
|
||||
}
|
||||
else if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePropertyOrSkip(value, prop, wrapper, context, nextDepth);
|
||||
}
|
||||
}
|
||||
WritePropertiesMarkerless(value, wrapper, context, nextDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < propCount; i++)
|
||||
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))
|
||||
{
|
||||
var prop = properties[i];
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
continue;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
var prop = properties[i];
|
||||
|
||||
if (prop.ExpectedTypeCode.HasValue)
|
||||
{
|
||||
WritePropertyMarkerless(value, prop, context);
|
||||
}
|
||||
else if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePropertyOrSkip(value, prop, wrapper, context, nextDepth);
|
||||
}
|
||||
}
|
||||
|
|
@ -1330,10 +1347,7 @@ public static partial class AcBinarySerializer
|
|||
/// Cold path: polymorphism is rare, NoInlining call overhead acceptable.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void WritePolymorphicMarker<TOutput>(
|
||||
BinarySerializationContext<TOutput> context,
|
||||
Type polyRuntimeType,
|
||||
int cachedObjectCacheIndex)
|
||||
private static void WritePolymorphicMarker<TOutput>(BinarySerializationContext<TOutput> context, Type polyRuntimeType, int cachedObjectCacheIndex)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
if (cachedObjectCacheIndex >= 0)
|
||||
|
|
@ -1391,7 +1405,7 @@ public static partial class AcBinarySerializer
|
|||
// Poly marker (handles combined poly+ref)
|
||||
WritePolymorphicMarker(context, polyRuntimeType, cachedObjectCacheIndex);
|
||||
|
||||
WriteObjectProperties(value, metadata, wrapper, context, depth, false);
|
||||
WriteObjectProperties(value, wrapper, context, depth, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1443,83 +1457,83 @@ public static partial class AcBinarySerializer
|
|||
/// <summary>
|
||||
/// Writes a property value using typed getters to avoid boxing.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WritePropertyValue<TOutput>(object obj, BinaryPropertyAccessor prop, BinarySerializationContext<TOutput> context, int depth)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
switch (prop.AccessorType)
|
||||
{
|
||||
case PropertyAccessorType.Int32:
|
||||
WriteInt32(prop.GetInt32(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.Int64:
|
||||
WriteInt64(prop.GetInt64(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.Boolean:
|
||||
context.WriteByte(prop.GetBoolean(obj) ? BinaryTypeCode.True : BinaryTypeCode.False);
|
||||
return;
|
||||
case PropertyAccessorType.Double:
|
||||
WriteFloat64Unsafe(prop.GetDouble(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.Single:
|
||||
WriteFloat32Unsafe(prop.GetSingle(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.Decimal:
|
||||
WriteDecimalUnsafe(prop.GetDecimal(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.DateTime:
|
||||
WriteDateTimeUnsafe(prop.GetDateTime(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.Byte:
|
||||
context.WriteByte(BinaryTypeCode.UInt8);
|
||||
context.WriteByte(prop.GetByte(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Int16:
|
||||
WriteInt16Unsafe(prop.GetInt16(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.UInt16:
|
||||
WriteUInt16Unsafe(prop.GetUInt16(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.UInt32:
|
||||
WriteUInt32(prop.GetUInt32(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.UInt64:
|
||||
WriteUInt64(prop.GetUInt64(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.Guid:
|
||||
WriteGuidUnsafe(prop.GetGuid(obj), context);
|
||||
return;
|
||||
case PropertyAccessorType.Enum:
|
||||
var enumValue = prop.GetEnumAsInt32(obj);
|
||||
if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny))
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.Enum);
|
||||
context.WriteByte(tiny);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.Enum);
|
||||
context.WriteByte(BinaryTypeCode.Int32);
|
||||
context.WriteVarInt(enumValue);
|
||||
}
|
||||
return;
|
||||
case PropertyAccessorType.String:
|
||||
{
|
||||
// Fast path: typed getter, no boxing, no Type.GetTypeCode() call
|
||||
var strValue = prop.GetString(obj);
|
||||
if (strValue != null)
|
||||
WriteString(strValue, context);
|
||||
else
|
||||
context.WriteByte(BinaryTypeCode.Null);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
// Fallback to object getter for reference types
|
||||
var value = prop.GetValue(obj);
|
||||
WriteValue(value, prop.PropertyType, context, depth);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//private static void WritePropertyValue<TOutput>(object obj, BinaryPropertyAccessor prop, BinarySerializationContext<TOutput> context, int depth)
|
||||
// where TOutput : struct, IBinaryOutputBase
|
||||
//{
|
||||
// switch (prop.AccessorType)
|
||||
// {
|
||||
// case PropertyAccessorType.Int32:
|
||||
// WriteInt32(prop.GetInt32(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.Int64:
|
||||
// WriteInt64(prop.GetInt64(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.Boolean:
|
||||
// context.WriteByte(prop.GetBoolean(obj) ? BinaryTypeCode.True : BinaryTypeCode.False);
|
||||
// return;
|
||||
// case PropertyAccessorType.Double:
|
||||
// WriteFloat64Unsafe(prop.GetDouble(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.Single:
|
||||
// WriteFloat32Unsafe(prop.GetSingle(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.Decimal:
|
||||
// WriteDecimalUnsafe(prop.GetDecimal(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.DateTime:
|
||||
// WriteDateTimeUnsafe(prop.GetDateTime(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.Byte:
|
||||
// context.WriteByte(BinaryTypeCode.UInt8);
|
||||
// context.WriteByte(prop.GetByte(obj));
|
||||
// return;
|
||||
// case PropertyAccessorType.Int16:
|
||||
// WriteInt16Unsafe(prop.GetInt16(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.UInt16:
|
||||
// WriteUInt16Unsafe(prop.GetUInt16(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.UInt32:
|
||||
// WriteUInt32(prop.GetUInt32(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.UInt64:
|
||||
// WriteUInt64(prop.GetUInt64(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.Guid:
|
||||
// WriteGuidUnsafe(prop.GetGuid(obj), context);
|
||||
// return;
|
||||
// case PropertyAccessorType.Enum:
|
||||
// var enumValue = prop.GetEnumAsInt32(obj);
|
||||
// if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny))
|
||||
// {
|
||||
// context.WriteByte(BinaryTypeCode.Enum);
|
||||
// context.WriteByte(tiny);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// context.WriteByte(BinaryTypeCode.Enum);
|
||||
// context.WriteByte(BinaryTypeCode.Int32);
|
||||
// context.WriteVarInt(enumValue);
|
||||
// }
|
||||
// return;
|
||||
// case PropertyAccessorType.String:
|
||||
// {
|
||||
// // Fast path: typed getter, no boxing, no Type.GetTypeCode() call
|
||||
// var strValue = prop.GetString(obj);
|
||||
// if (strValue != null)
|
||||
// WriteString(strValue, context);
|
||||
// else
|
||||
// context.WriteByte(BinaryTypeCode.Null);
|
||||
// return;
|
||||
// }
|
||||
// default:
|
||||
// // Fallback to object getter for reference types
|
||||
// var value = prop.GetValue(obj);
|
||||
// WriteValue(value, prop.PropertyType, context, depth);
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a property value OR a skip marker if the value is default/null.
|
||||
|
|
|
|||
Loading…
Reference in New Issue