Optimize ref/interning checks; add ScanOnly for benchmarking
Refactor serialization context to use precomputed boolean flags (HasRefHandling, HasAllRefHandling, HasStringInterning) for faster reference and string interning checks, replacing repeated enum comparisons. Update source generator to emit code using these flags. Add AcBinarySerializer.ScanOnly for isolated scan benchmarking. Set MaxDepth in test options. Improves performance and maintainability.
This commit is contained in:
parent
97ece85ee1
commit
7e3fbe7a52
|
|
@ -36,7 +36,8 @@
|
||||||
"Bash(del \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core\\\\Serializers\\\\Binaries\\\\IBinaryOutput.cs\")",
|
"Bash(del \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core\\\\Serializers\\\\Binaries\\\\IBinaryOutput.cs\")",
|
||||||
"Bash(sort:*)",
|
"Bash(sort:*)",
|
||||||
"WebFetch(domain:neuecc.medium.com)",
|
"WebFetch(domain:neuecc.medium.com)",
|
||||||
"WebFetch(domain:raw.githubusercontent.com)"
|
"WebFetch(domain:raw.githubusercontent.com)",
|
||||||
|
"Bash(xargs cat)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,16 @@ public static class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Serialize() => AcBinarySerializer.Serialize(_order, _options);
|
public void Serialize()
|
||||||
|
{
|
||||||
|
AcBinarySerializer.Serialize(_order, _options);
|
||||||
|
|
||||||
|
//if (_options.ReferenceHandling != ReferenceHandlingMode.None || _options.UseStringInterning != StringInterningMode.None)
|
||||||
|
//{
|
||||||
|
// AcBinarySerializer.ScanOnly(_order, _options);
|
||||||
|
//}
|
||||||
|
//else AcBinarySerializer.Serialize(_order, _options);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Deserialize() => AcBinaryDeserializer.Deserialize<TestOrder>(_serialized, _options);
|
public void Deserialize() => AcBinaryDeserializer.Deserialize<TestOrder>(_serialized, _options);
|
||||||
|
|
|
||||||
|
|
@ -498,7 +498,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine("using System.Runtime.CompilerServices;");
|
sb.AppendLine("using System.Runtime.CompilerServices;");
|
||||||
sb.AppendLine("using System.Runtime.InteropServices;");
|
sb.AppendLine("using System.Runtime.InteropServices;");
|
||||||
sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
|
sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
|
||||||
// ReferenceHandlingMode is needed for ScanObject self ref tracking and direct object write/scan
|
// IGeneratedBinaryWriter and other serializer types
|
||||||
sb.AppendLine("using AyCode.Core.Serializers;");
|
sb.AppendLine("using AyCode.Core.Serializers;");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
if (!string.IsNullOrEmpty(ci.Namespace))
|
if (!string.IsNullOrEmpty(ci.Namespace))
|
||||||
|
|
@ -566,11 +566,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
if (!ci.NeedsIdScan)
|
if (!ci.NeedsIdScan)
|
||||||
{
|
{
|
||||||
if (ci.NeedsAllRefScan && ci.NeedsInternScan)
|
if (ci.NeedsAllRefScan && ci.NeedsInternScan)
|
||||||
sb.AppendLine(" if (context.ReferenceHandling != ReferenceHandlingMode.All && !context.UseStringInterning) return;");
|
sb.AppendLine(" if (!context.HasAllRefHandling && !context.HasStringInterning) return;");
|
||||||
else if (ci.NeedsAllRefScan)
|
else if (ci.NeedsAllRefScan)
|
||||||
sb.AppendLine(" if (context.ReferenceHandling != ReferenceHandlingMode.All) return;");
|
sb.AppendLine(" if (!context.HasAllRefHandling) return;");
|
||||||
else if (ci.NeedsInternScan)
|
else if (ci.NeedsInternScan)
|
||||||
sb.AppendLine(" if (!context.UseStringInterning) return;");
|
sb.AppendLine(" if (!context.HasStringInterning) return;");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Null/depth guard — matches runtime ScanValue entry
|
// Null/depth guard — matches runtime ScanValue entry
|
||||||
|
|
@ -590,7 +590,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
_ => "TryTrackInt32"
|
_ => "TryTrackInt32"
|
||||||
};
|
};
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine(" if (context.ReferenceHandling != ReferenceHandlingMode.None)");
|
sb.AppendLine(" if (context.HasRefHandling)");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));");
|
sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));");
|
||||||
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
||||||
|
|
@ -607,7 +607,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
{
|
{
|
||||||
// Non-IId type: track via wrapper.TryTrackInt32 with RuntimeHelpers.GetHashCode
|
// Non-IId type: track via wrapper.TryTrackInt32 with RuntimeHelpers.GetHashCode
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine(" if (context.ReferenceHandling == ReferenceHandlingMode.All)");
|
sb.AppendLine(" if (context.HasAllRefHandling)");
|
||||||
sb.AppendLine(" {");
|
sb.AppendLine(" {");
|
||||||
sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));");
|
sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));");
|
||||||
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;");
|
||||||
|
|
@ -914,11 +914,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
if (!p.ChildNeedsIdScan)
|
if (!p.ChildNeedsIdScan)
|
||||||
{
|
{
|
||||||
if (p.ChildNeedsAllRefScan && p.ChildNeedsInternScan)
|
if (p.ChildNeedsAllRefScan && p.ChildNeedsInternScan)
|
||||||
guard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning";
|
guard = "context.HasAllRefHandling || context.HasStringInterning";
|
||||||
else if (p.ChildNeedsAllRefScan)
|
else if (p.ChildNeedsAllRefScan)
|
||||||
guard = "context.ReferenceHandling == ReferenceHandlingMode.All";
|
guard = "context.HasAllRefHandling";
|
||||||
else if (p.ChildNeedsInternScan)
|
else if (p.ChildNeedsInternScan)
|
||||||
guard = "context.UseStringInterning";
|
guard = "context.HasStringInterning";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (guard != null)
|
if (guard != null)
|
||||||
|
|
@ -1018,11 +1018,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
if (!p.ElementNeedsIdScan)
|
if (!p.ElementNeedsIdScan)
|
||||||
{
|
{
|
||||||
if (p.ElementNeedsAllRefScan && p.ElementNeedsInternScan)
|
if (p.ElementNeedsAllRefScan && p.ElementNeedsInternScan)
|
||||||
elemGuard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning";
|
elemGuard = "context.HasAllRefHandling || context.HasStringInterning";
|
||||||
else if (p.ElementNeedsAllRefScan)
|
else if (p.ElementNeedsAllRefScan)
|
||||||
elemGuard = "context.ReferenceHandling == ReferenceHandlingMode.All";
|
elemGuard = "context.HasAllRefHandling";
|
||||||
else if (p.ElementNeedsInternScan)
|
else if (p.ElementNeedsInternScan)
|
||||||
elemGuard = "context.UseStringInterning";
|
elemGuard = "context.HasStringInterning";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard entire collection scan with runtime check when no IId in element subtree
|
// Guard entire collection scan with runtime check when no IId in element subtree
|
||||||
|
|
@ -1107,11 +1107,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
if (hasComplexValues && p.DictValueNeedsScan && !p.DictValueNeedsIdScan)
|
if (hasComplexValues && p.DictValueNeedsScan && !p.DictValueNeedsIdScan)
|
||||||
{
|
{
|
||||||
if (p.DictValueNeedsAllRefScan && p.DictValueNeedsInternScan)
|
if (p.DictValueNeedsAllRefScan && p.DictValueNeedsInternScan)
|
||||||
complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning";
|
complexGuard = "context.HasAllRefHandling || context.HasStringInterning";
|
||||||
else if (p.DictValueNeedsAllRefScan)
|
else if (p.DictValueNeedsAllRefScan)
|
||||||
complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All";
|
complexGuard = "context.HasAllRefHandling";
|
||||||
else if (p.DictValueNeedsInternScan)
|
else if (p.DictValueNeedsInternScan)
|
||||||
complexGuard = "context.UseStringInterning";
|
complexGuard = "context.HasStringInterning";
|
||||||
}
|
}
|
||||||
|
|
||||||
// For string-only scan (no complex values), use simple interning loop
|
// For string-only scan (no complex values), use simple interning loop
|
||||||
|
|
@ -1257,8 +1257,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
{
|
{
|
||||||
// Ref tracking possible, no metadata — Object or ObjectRefFirst/ObjectRef
|
// Ref tracking possible, no metadata — Object or ObjectRefFirst/ObjectRef
|
||||||
var refGuard = p.IsIId
|
var refGuard = p.IsIId
|
||||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
? "context.HasRefHandling"
|
||||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
: "context.HasAllRefHandling";
|
||||||
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
||||||
|
|
@ -1284,8 +1284,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
// Full path: ref tracking + metadata
|
// Full path: ref tracking + metadata
|
||||||
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({p.TypeNameForTypeof})));");
|
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({p.TypeNameForTypeof})));");
|
||||||
var refGuard = p.IsIId
|
var refGuard = p.IsIId
|
||||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
? "context.HasRefHandling"
|
||||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
: "context.HasAllRefHandling";
|
||||||
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
||||||
|
|
@ -1436,8 +1436,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
{
|
{
|
||||||
// Inline ref tracking
|
// Inline ref tracking
|
||||||
var elemRefGuard = p.ElementIsIId
|
var elemRefGuard = p.ElementIsIId
|
||||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
? "context.HasRefHandling"
|
||||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
: "context.HasAllRefHandling";
|
||||||
|
|
||||||
if (!p.ElementEnableMetadata)
|
if (!p.ElementEnableMetadata)
|
||||||
{
|
{
|
||||||
|
|
@ -1700,8 +1700,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var dvRefGuard = p.DictValueIsIId
|
var dvRefGuard = p.DictValueIsIId
|
||||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
? "context.HasRefHandling"
|
||||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
: "context.HasAllRefHandling";
|
||||||
|
|
||||||
if (!p.DictValueEnableMetadata)
|
if (!p.DictValueEnableMetadata)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,8 @@ public class AcBinarySerializerIIdReferenceTests
|
||||||
{
|
{
|
||||||
ReferenceHandling = mode,
|
ReferenceHandling = mode,
|
||||||
UseGeneratedCode = useSgen,
|
UseGeneratedCode = useSgen,
|
||||||
UseMetadata = useMeta
|
UseMetadata = useMeta,
|
||||||
|
MaxDepth = 10
|
||||||
};
|
};
|
||||||
|
|
||||||
Console.WriteLine($"\n========== ReferenceHandling: {options.ReferenceHandling}, UseSgen: {options.UseGeneratedCode}, UseMeta: {options.UseMetadata} ==========");
|
Console.WriteLine($"\n========== ReferenceHandling: {options.ReferenceHandling}, UseSgen: {options.UseGeneratedCode}, UseMeta: {options.UseMetadata} ==========");
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,17 @@ public abstract class AcSerializerContextBase<TMetadata, TOptions>
|
||||||
private bool _hasRefHandling;
|
private bool _hasRefHandling;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pre-computed: ReferenceHandling == IId (not All). When true, only IId types are tracked.
|
/// Pre-computed: ReferenceHandling == OnlyId (not All). When true, only IId types are tracked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool _hasIdHandling;
|
private bool _hasIdHandling;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-computed: ReferenceHandling == All. When true, all reference types are tracked.
|
||||||
|
/// </summary>
|
||||||
|
private bool _hasAllRefHandling;
|
||||||
|
|
||||||
|
internal bool HasRefHandling => _hasRefHandling;
|
||||||
|
internal bool HasAllRefHandling => _hasAllRefHandling;
|
||||||
public bool ThrowOnCircularReference => Options.ThrowOnCircularReference;
|
public bool ThrowOnCircularReference => Options.ThrowOnCircularReference;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Global shared cache for metadata (thread-safe, shared across all contexts).
|
/// Global shared cache for metadata (thread-safe, shared across all contexts).
|
||||||
|
|
@ -165,6 +173,7 @@ public abstract class AcSerializerContextBase<TMetadata, TOptions>
|
||||||
Options = options;
|
Options = options;
|
||||||
_hasRefHandling = options.ReferenceHandling != ReferenceHandlingMode.None;
|
_hasRefHandling = options.ReferenceHandling != ReferenceHandlingMode.None;
|
||||||
_hasIdHandling = options.ReferenceHandling == ReferenceHandlingMode.OnlyId;
|
_hasIdHandling = options.ReferenceHandling == ReferenceHandlingMode.OnlyId;
|
||||||
|
_hasAllRefHandling = options.ReferenceHandling == ReferenceHandlingMode.All;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,8 @@ public static partial class AcBinarySerializer
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// These properties delegate to Options for convenience
|
// These properties delegate to Options for convenience
|
||||||
internal bool UseStringInterning => Options.UseStringInterning != StringInterningMode.None;
|
internal bool HasStringInterning { get; private set; }
|
||||||
|
internal bool UseStringInterning => HasStringInterning;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pre-computed <c>1 << (int)Options.UseStringInterning</c>.
|
/// Pre-computed <c>1 << (int)Options.UseStringInterning</c>.
|
||||||
|
|
@ -230,7 +231,7 @@ public static partial class AcBinarySerializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if we have interning/ref tracking (cache count needed in header).
|
/// True if we have interning/ref tracking (cache count needed in header).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasCaching => UseStringInterning || ReferenceHandling != ReferenceHandlingMode.None;
|
public bool HasCaching => HasStringInterning || HasRefHandling;
|
||||||
public bool UseMetadata => Options.UseMetadata;
|
public bool UseMetadata => Options.UseMetadata;
|
||||||
public bool UseGeneratedCode => Options.UseGeneratedCode;
|
public bool UseGeneratedCode => Options.UseGeneratedCode;
|
||||||
|
|
||||||
|
|
@ -279,6 +280,7 @@ public static partial class AcBinarySerializer
|
||||||
base.Reset(options);
|
base.Reset(options);
|
||||||
HasPropertyFilter = Options.PropertyFilter != null;
|
HasPropertyFilter = Options.PropertyFilter != null;
|
||||||
InternBit = 1 << (int)Options.UseStringInterning;
|
InternBit = 1 << (int)Options.UseStringInterning;
|
||||||
|
HasStringInterning = Options.UseStringInterning != StringInterningMode.None;
|
||||||
//FastWire = Options.WireMode == WireMode.Fast;
|
//FastWire = Options.WireMode == WireMode.Fast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,30 @@ public static partial class AcBinarySerializer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs only the scan pass (ScanForDuplicates) without writing.
|
||||||
|
/// For benchmarking scan pass overhead in isolation.
|
||||||
|
/// </summary>
|
||||||
|
internal static void ScanOnly<T>(T value, AcBinarySerializerOptions options)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
var runtimeType = value.GetType();
|
||||||
|
var context = BinarySerializationContextPool<ArrayBinaryOutput>.Get(options);
|
||||||
|
if (!context.OutputInitialized)
|
||||||
|
{
|
||||||
|
context.Output = new ArrayBinaryOutput(options.InitialBufferCapacity);
|
||||||
|
context.OutputInitialized = true;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ScanForDuplicates(value, runtimeType, context);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
BinarySerializationContextPool<ArrayBinaryOutput>.Return(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize object to an IBufferWriter for zero-copy scenarios.
|
/// Serialize object to an IBufferWriter for zero-copy scenarios.
|
||||||
/// Uses BufferWriterBinaryOutput — writes directly to the caller's buffer.
|
/// Uses BufferWriterBinaryOutput — writes directly to the caller's buffer.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue