diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBenchmark.cs new file mode 100644 index 0000000..e77a187 --- /dev/null +++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBenchmark.cs @@ -0,0 +1,46 @@ +using AyCode.Core.Serializers.Binaries; +using AyCode.Core.Tests.TestModels; +using System.Runtime.CompilerServices; + +namespace AyCode.Core.Serializers.Console.Benchmarks; + +/// +/// AcBinary benchmark, Byte[] I/O mode. The headline AcBinary row in every cell — compared +/// against as the SOTA baseline. +/// +internal sealed class AcBinaryBenchmark : ISerializerBenchmark +{ + private readonly TestOrder _order; + private readonly AcBinarySerializerOptions _options; + private readonly byte[] _serialized; + + public string Engine => Configuration.EngineAcBinary; + public string IoMode => Configuration.IoByteArray; + public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime; + public string OptionsPreset { get; } + public int SerializedSize => _serialized.Length; + public long SetupSerializeAllocBytes => 0; + public long SetupDeserializeAllocBytes => 0; + public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options); + + public AcBinaryBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) + { + _order = order; + _options = options; + OptionsPreset = optionsPreset; + _serialized = AcBinarySerializer.Serialize(order, options); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Serialize() => AcBinarySerializer.Serialize(_order, _options); + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Deserialize() => AcBinaryDeserializer.Deserialize(_serialized, _options); + + public bool VerifyRoundTrip() + { + var bytes = AcBinarySerializer.Serialize(_order, _options); + var roundTripped = AcBinaryDeserializer.Deserialize(bytes, _options); + return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); + } +} diff --git a/AyCode.Core.Serializers.Console/Benchmarks/BenchmarkOptions.cs b/AyCode.Core.Serializers.Console/Benchmarks/BenchmarkOptions.cs new file mode 100644 index 0000000..7050fdc --- /dev/null +++ b/AyCode.Core.Serializers.Console/Benchmarks/BenchmarkOptions.cs @@ -0,0 +1,55 @@ +using AyCode.Core.Serializers.Binaries; +using MemoryPack; + +namespace AyCode.Core.Serializers.Console.Benchmarks; + +/// +/// Per-engine options-formatting + selection helpers shared by all benchmark rows. Centralizes +/// the Options-column display string (so the .log / .LLM / console headers stay consistent) and +/// the MemoryPack WireMode-aligned options selection (so AcBinary FastWire ↔ MemoryPack +/// UTF-16 comparisons stay apples-to-apples). +/// +internal static class BenchmarkOptions +{ + /// + /// Common Options-column formatter for every AcBinary serializer benchmark row. Renders the + /// configured options-level value AND the effective attribute-level enable flag side-by-side + /// (e.g. Interning=All(opt) | False (attr)) so attribute-suppressed features cannot + /// silently mislead. Pass any benchmark-specific extras (e.g. ", BufferSize=4096B") + /// in — they are appended after the common fields. + /// + internal static string BuildAcBinary(AcBinarySerializerOptions options, string extra = "") + { + // PropertyFilter: opt-side is "Set"/"None" depending on whether a callback is registered (the callback + // itself isn't a meaningful display value); attr-side is the cross-type-aggregated bool (true = every + // tagged type has the feature enabled, false = at least one type opted out via + // [AcBinarySerializable(enablePropertyFilterFeature: false)] → SGen-emit + Runtime hot-loop both gate). + var propFilterOpt = options.PropertyFilter == null ? "None" : "Set"; + + return $"WireMode={options.WireMode}, " + + $"RefHandling={options.ReferenceHandling}(opt) | {Configuration.AttrFlags.refHandling} (attr), " + + $"Interning={options.UseStringInterning}(opt) | {Configuration.AttrFlags.internString} (attr), " + + $"Metadata={options.UseMetadata}(opt) | {Configuration.AttrFlags.metadata} (attr), " + + $"PropertyFilter={propFilterOpt}(opt) | {Configuration.AttrFlags.propertyFilter} (attr), " + + $"SGen={options.UseGeneratedCode}, " + + $"Compression={options.UseCompression}{extra}"; + } + + /// + /// Returns MemoryPack serializer options aligned with for a fair + /// apples-to-apples wire-format comparison: + /// + /// (UTF-8) — both + /// engines encode UTF-8, comparison is purely about header / tier / dispatch overhead. + /// (UTF-16 raw memcpy) — + /// both engines write UTF-16 raw bytes, so wire-size and CPU comparison reflect the same string-encoding family. + /// + /// Without this alignment the FastWire vs MemPack-default comparison conflates two unrelated dimensions + /// (UTF-16 raw vs UTF-8 encoded) and produces a misleading +40% wire-size delta that is structurally + /// the encoding-family difference, NOT an AcBinary-specific overhead. + /// + internal static MemoryPackSerializerOptions GetMemPack() => + Configuration.SelectedWireMode == WireMode.Fast + ? MemoryPackSerializerOptions.Utf16 + : MemoryPackSerializerOptions.Default; +} diff --git a/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBenchmark.cs new file mode 100644 index 0000000..52444e3 --- /dev/null +++ b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBenchmark.cs @@ -0,0 +1,47 @@ +using AyCode.Core.Tests.TestModels; +using MemoryPack; +using System.Runtime.CompilerServices; + +namespace AyCode.Core.Serializers.Console.Benchmarks; + +/// +/// MemoryPack benchmark, Byte[] I/O mode. The SOTA baseline AcBinary is compared against in every +/// cell. WireMode-aligned options via so Compact ↔ UTF-8 +/// and FastWire ↔ UTF-16 are apples-to-apples on the string-encoding axis. +/// +internal sealed class MemoryPackBenchmark : ISerializerBenchmark +{ + private readonly TestOrder _order; + private readonly MemoryPackSerializerOptions _options; + private readonly byte[] _serialized; + + public string Engine => Configuration.EngineMemoryPack; + public string IoMode => Configuration.IoByteArray; + public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters + public string OptionsPreset { get; } + public int SerializedSize => _serialized.Length; + public long SetupSerializeAllocBytes => 0; + public long SetupDeserializeAllocBytes => 0; + public string? OptionsDescription => $"StringEncoding={_options.StringEncoding}"; + + public MemoryPackBenchmark(TestOrder order, string optionsPreset) + { + _order = order; + OptionsPreset = optionsPreset; + _options = BenchmarkOptions.GetMemPack(); + _serialized = MemoryPackSerializer.Serialize(order, _options); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Serialize() => MemoryPackSerializer.Serialize(_order, _options); + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Deserialize() => MemoryPackSerializer.Deserialize(_serialized, _options); + + public bool VerifyRoundTrip() + { + var bytes = MemoryPackSerializer.Serialize(_order, _options); + var roundTripped = MemoryPackSerializer.Deserialize(bytes, _options); + return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); + } +} diff --git a/AyCode.Core.Serializers.Console/Benchmarks/MessagePackBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/MessagePackBenchmark.cs new file mode 100644 index 0000000..7f4e2b0 --- /dev/null +++ b/AyCode.Core.Serializers.Console/Benchmarks/MessagePackBenchmark.cs @@ -0,0 +1,58 @@ +#if !AYCODE_NATIVEAOT +using AyCode.Core.Tests.TestModels; +using MessagePack; +using MessagePack.Resolvers; +using System.Runtime.CompilerServices; + +namespace AyCode.Core.Serializers.Console.Benchmarks; + +/// +/// MessagePack benchmark, Byte[] I/O mode. Excluded from NativeAOT build because v3's StandardResolver +/// falls back to DynamicGenericResolver for closed-generic types (List<TestOrderItem> et al.), +/// which uses Activator.CreateInstance on formatter types the AOT trimmer drops → +/// MissingMethodException at runtime. Available for regular JIT runs (dotnet run) only. +/// +internal sealed class MessagePackBenchmark : ISerializerBenchmark +{ + private readonly TestOrder _order; + private readonly MessagePackSerializerOptions _options; + private readonly byte[] _serialized; + + public string Engine => Configuration.EngineMessagePack; + public string IoMode => Configuration.IoByteArray; + public string DispatchMode => Configuration.ModeSGen; // MessagePack uses [MessagePackObject] source-generated formatters (StandardResolver) + public string OptionsPreset { get; } + public int SerializedSize => _serialized.Length; + public long SetupSerializeAllocBytes => 0; + public long SetupDeserializeAllocBytes => 0; + public string OptionsDescription { get; } + + public MessagePackBenchmark(TestOrder order, string optionsPreset) + { + _order = order; + OptionsPreset = optionsPreset; + + //_options = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); + //_options = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4Block); + _options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.None); + + var isContractless = _options.Resolver is ContractlessStandardResolver; + OptionsDescription = $"Mode={( isContractless ? "Contractless" : "ContractBased")}, Compression={_options.Compression}"; + + _serialized = MessagePackSerializer.Serialize(order, _options); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Serialize() => MessagePackSerializer.Serialize(_order, _options); + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Deserialize() => MessagePackSerializer.Deserialize(_serialized, _options); + + public bool VerifyRoundTrip() + { + var bytes = MessagePackSerializer.Serialize(_order, _options); + var roundTripped = MessagePackSerializer.Deserialize(bytes, _options); + return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); + } +} +#endif diff --git a/AyCode.Core.Serializers.Console/Benchmarks/SystemTextJsonBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/SystemTextJsonBenchmark.cs new file mode 100644 index 0000000..2acca54 --- /dev/null +++ b/AyCode.Core.Serializers.Console/Benchmarks/SystemTextJsonBenchmark.cs @@ -0,0 +1,54 @@ +using AyCode.Core.Tests.TestModels; +using System.Runtime.CompilerServices; +using System.Text.Json; + +namespace AyCode.Core.Serializers.Console.Benchmarks; + +/// +/// System.Text.Json benchmark, String I/O mode. Reference comparison — uses reflection-based metadata +/// (no source-generator opt-in here). Typically NOT in the active suite (commented out in +/// BenchmarkLoop.CreateSerializers); ranks far behind binary serializers on µs/op but provides +/// a familiar JSON baseline when needed. +/// +internal sealed class SystemTextJsonBenchmark : ISerializerBenchmark +{ + private readonly TestOrder _order; + private readonly JsonSerializerOptions _options; + private readonly string _serialized; + private readonly byte[] _serializedUtf8; + + public string Engine => Configuration.EngineSystemTextJson; + public string IoMode => Configuration.IoString; + public string DispatchMode => Configuration.ModeRuntime; // System.Text.Json default uses reflection-based metadata (no source generator opt-in here) + public string OptionsPreset { get; } + public int SerializedSize => _serializedUtf8.Length; + public long SetupSerializeAllocBytes => 0; + public long SetupDeserializeAllocBytes => 0; + + public SystemTextJsonBenchmark(TestOrder order, string optionsPreset) + { + _order = order; + OptionsPreset = optionsPreset; + _options = new JsonSerializerOptions + { + WriteIndented = false, + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles + }; + _serialized = JsonSerializer.Serialize(order, _options); + _serializedUtf8 = Configuration.Utf8NoBom.GetBytes(_serialized); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Serialize() => JsonSerializer.Serialize(_order, _options); + + [MethodImpl(MethodImplOptions.NoInlining)] + public void Deserialize() => JsonSerializer.Deserialize(_serialized, _options); + + public bool VerifyRoundTrip() + { + var json = JsonSerializer.Serialize(_order, _options); + var roundTripped = JsonSerializer.Deserialize(json, _options); + return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); + } +} diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs index 49cec85..19501ed 100644 --- a/AyCode.Core.Serializers.Console/Program.cs +++ b/AyCode.Core.Serializers.Console/Program.cs @@ -35,66 +35,7 @@ namespace AyCode.Core.Serializers.Console; public static class Program { // Configuration (constants, mutable state, attribute-flag aggregation) → Configuration.cs - - /// - /// Common Options-column formatter for every AcBinary serializer benchmark row. Renders the - /// configured options-level value AND the effective attribute-level enable flag side-by-side - /// (e.g. Interning=All(opt) | False (attr)) so attribute-suppressed features cannot - /// silently mislead. Pass any benchmark-specific extras (e.g. ", BufferSize=4096B") - /// in — they are appended after the common fields. - /// - private static string BuildAcBinaryOptionsDescription(AcBinarySerializerOptions options, string extra = "") - { - // PropertyFilter: opt-side is "Set"/"None" depending on whether a callback is registered (the callback - // itself isn't a meaningful display value); attr-side is the cross-type-aggregated bool (true = every - // tagged type has the feature enabled, false = at least one type opted out via - // [AcBinarySerializable(enablePropertyFilterFeature: false)] → SGen-emit + Runtime hot-loop both gate). - var propFilterOpt = options.PropertyFilter == null ? "None" : "Set"; - - return $"WireMode={options.WireMode}, " + - $"RefHandling={options.ReferenceHandling}(opt) | {Configuration.AttrFlags.refHandling} (attr), " + - $"Interning={options.UseStringInterning}(opt) | {Configuration.AttrFlags.internString} (attr), " + - $"Metadata={options.UseMetadata}(opt) | {Configuration.AttrFlags.metadata} (attr), " + - $"PropertyFilter={propFilterOpt}(opt) | {Configuration.AttrFlags.propertyFilter} (attr), " + - $"SGen={options.UseGeneratedCode}, " + - $"Compression={options.UseCompression}{extra}"; - } - - /// - /// Returns MemoryPack serializer options aligned with for a fair - /// apples-to-apples wire-format comparison: - /// - /// (UTF-8) — both - /// engines encode UTF-8, comparison is purely about header / tier / dispatch overhead. - /// (UTF-16 raw memcpy) — - /// both engines write UTF-16 raw bytes, so wire-size and CPU comparison reflect the same string-encoding family. - /// - /// Without this alignment the FastWire vs MemPack-default comparison conflates two unrelated dimensions - /// (UTF-16 raw vs UTF-8 encoded) and produces a misleading +40% wire-size delta that is structurally - /// the encoding-family difference, NOT an AcBinary-specific overhead. - /// - private static MemoryPackSerializerOptions GetMemPackOptions() => - Configuration.SelectedWireMode == WireMode.Fast - ? MemoryPackSerializerOptions.Utf16 - : MemoryPackSerializerOptions.Default; - - /// - /// Converts a total-time (in ms across ) into per-operation microseconds. - /// Formula: totalMs / iterations × 1000. The benchmark stores *TimeMs as the cumulative - /// median over the timing run; the display layer renders per-op µs to make numbers iteration-count - /// independent (e.g. switching Configuration.TestIterations 1000 → 100 leaves the displayed µs/op unchanged - /// — only its sample noise grows). Symmetric with the already-per-op *AllocBytesPerOp fields. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - - /// - /// Converts a total-time (in ms across ) into per-operation microseconds. - /// Per-op µs is the iter-independent unit: 1000 iter and 50000 iter of the same operation should - /// produce the same per-op µs (within noise). Necessary because per-cell adaptive iteration makes - /// iterations a per-row property — there is no longer a single global Configuration.TestIterations to divide by. - /// - // Output helpers (PrintResult, SaveResults, OverallStats, FormatMicrosWithRange, etc.) → Output.cs - // BenchmarkResult DTO → BenchmarkResult.cs + // BuildAcBinary + GetMemPack helpers → Benchmarks/BenchmarkOptions.cs public static void Main(string[] args) { @@ -637,141 +578,6 @@ private static List RunBenchmarksForTestData(TestDataSet testDa #region Serializer Implementations -internal sealed class AcBinaryBenchmark : ISerializerBenchmark - { - private readonly TestOrder _order; - private readonly AcBinarySerializerOptions _options; - private readonly byte[] _serialized; - - public string Engine => Configuration.EngineAcBinary; - public string IoMode => Configuration.IoByteArray; - public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime; - public string OptionsPreset { get; } - public int SerializedSize => _serialized.Length; - public long SetupSerializeAllocBytes => 0; - public long SetupDeserializeAllocBytes => 0; - public string OptionsDescription => BuildAcBinaryOptionsDescription(_options); - - public AcBinaryBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) - { - _order = order; - _options = options; - OptionsPreset = optionsPreset; - _serialized = AcBinarySerializer.Serialize(order, options); - - //_options.UseCompression = Lz4CompressionMode.Block; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - 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)] - public void Deserialize() => AcBinaryDeserializer.Deserialize(_serialized, _options); - - public bool VerifyRoundTrip() - { - var bytes = AcBinarySerializer.Serialize(_order, _options); - var roundTripped = AcBinaryDeserializer.Deserialize(bytes, _options); - return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); - } - } - - internal sealed class MemoryPackBenchmark : ISerializerBenchmark - { - private readonly TestOrder _order; - private readonly MemoryPackSerializerOptions _options; - private readonly byte[] _serialized; - - public string Engine => Configuration.EngineMemoryPack; - public string IoMode => Configuration.IoByteArray; - public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters - public string OptionsPreset { get; } - public int SerializedSize => _serialized.Length; - public long SetupSerializeAllocBytes => 0; - public long SetupDeserializeAllocBytes => 0; - public string? OptionsDescription => $"StringEncoding={_options.StringEncoding}"; - - public MemoryPackBenchmark(TestOrder order, string optionsPreset) - { - _order = order; - OptionsPreset = optionsPreset; - _options = GetMemPackOptions(); - _serialized = MemoryPackSerializer.Serialize(order, _options); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public void Serialize() => MemoryPackSerializer.Serialize(_order, _options); - - [MethodImpl(MethodImplOptions.NoInlining)] - public void Deserialize() => MemoryPackSerializer.Deserialize(_serialized, _options); - - public bool VerifyRoundTrip() - { - var bytes = MemoryPackSerializer.Serialize(_order, _options); - var roundTripped = MemoryPackSerializer.Deserialize(bytes, _options); - return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); - } - } - -#if !AYCODE_NATIVEAOT - // MessagePack benchmark — excluded from NativeAOT build because v3's StandardResolver falls back - // to DynamicGenericResolver for closed-generic types (List et al.), which uses - // Activator.CreateInstance on formatter types the AOT trimmer drops → MissingMethodException at runtime. - // Available for regular JIT runs (`dotnet run`) only. - internal sealed class MessagePackBenchmark : ISerializerBenchmark - { - private readonly TestOrder _order; - private readonly MessagePackSerializerOptions _options; - private readonly byte[] _serialized; - - public string Engine => Configuration.EngineMessagePack; - public string IoMode => Configuration.IoByteArray; - public string DispatchMode => Configuration.ModeSGen; // MessagePack uses [MessagePackObject] source-generated formatters (StandardResolver) - public string OptionsPreset { get; } - public int SerializedSize => _serialized.Length; - public long SetupSerializeAllocBytes => 0; - public long SetupDeserializeAllocBytes => 0; - public string OptionsDescription { get; } - - public MessagePackBenchmark(TestOrder order, string optionsPreset) - { - _order = order; - OptionsPreset = optionsPreset; - - //_options = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); - //_options = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4Block); - _options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.None); - - var isContractless = _options.Resolver is ContractlessStandardResolver; - OptionsDescription = $"Mode={( isContractless ? "Contractless" : "ContractBased")}, Compression={_options.Compression}"; - - _serialized = MessagePackSerializer.Serialize(order, _options); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public void Serialize() => MessagePackSerializer.Serialize(_order, _options); - - [MethodImpl(MethodImplOptions.NoInlining)] - public void Deserialize() => MessagePackSerializer.Deserialize(_serialized, _options); - - public bool VerifyRoundTrip() - { - var bytes = MessagePackSerializer.Serialize(_order, _options); - var roundTripped = MessagePackSerializer.Deserialize(bytes, _options); - return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); - } - } -#endif - /// /// Benchmarks AcBinary via the IBufferWriter overload with a pre-allocated, reused ArrayBufferWriter. /// Realistic IBufferWriter usage pattern: caller owns + reuses the writer (zero alloc per call after warmup). @@ -796,7 +602,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark public int SerializedSize => _serialized.Length; public long SetupSerializeAllocBytes => 0; public long SetupDeserializeAllocBytes => 0; - public string OptionsDescription => BuildAcBinaryOptionsDescription(_options, $", BufferSize={_options.BufferWriterChunkSize}B"); + public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options, $", BufferSize={_options.BufferWriterChunkSize}B"); public AcBinaryFreshBufferWriterBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) { @@ -896,7 +702,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark public long SetupSerializeAllocBytes { get; } public long SetupDeserializeAllocBytes { get; } public bool IsRoundTripOnly => true; - public string OptionsDescription => BuildAcBinaryOptionsDescription(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=NamedPipe(long-lived,multiMessage,2-task)"); + public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=NamedPipe(long-lived,multiMessage,2-task)"); public AcBinaryNamedPipeBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) { @@ -1111,7 +917,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark public long SetupSerializeAllocBytes { get; } public long SetupDeserializeAllocBytes { get; } public bool IsRoundTripOnly => true; - public string OptionsDescription => BuildAcBinaryOptionsDescription(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=Pipe(in-memory,multiMessage,2-task)"); + public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=Pipe(in-memory,multiMessage,2-task)"); public AcBinaryInMemoryPipeBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) { @@ -1297,7 +1103,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark public long SetupSerializeAllocBytes { get; } public long SetupDeserializeAllocBytes { get; } public bool IsRoundTripOnly => true; - public string OptionsDescription => BuildAcBinaryOptionsDescription(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=NamedPipe(raw,2-task)"); + public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=NamedPipe(raw,2-task)"); public AcBinaryNamedPipeRawByteArrayBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) { @@ -1494,7 +1300,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark public long SetupSerializeAllocBytes { get; } public long SetupDeserializeAllocBytes { get; } public bool IsRoundTripOnly => true; - public string OptionsDescription => BuildAcBinaryOptionsDescription(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=in-memory(raw,2-task)"); + public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options, $", BufferSize={_options.BufferWriterChunkSize}B, Transport=in-memory(raw,2-task)"); public AcBinaryInMemoryRawByteArrayBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) { @@ -1638,7 +1444,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark { _order = order; OptionsPreset = optionsPreset; - _options = GetMemPackOptions(); + _options = BenchmarkOptions.GetMemPack(); _serialized = MemoryPackSerializer.Serialize(order, _options); } @@ -1677,7 +1483,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark public int SerializedSize => _serialized.Length; public long SetupSerializeAllocBytes { get; } public long SetupDeserializeAllocBytes => 0; - public string OptionsDescription => BuildAcBinaryOptionsDescription(_options); + public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options); public AcBinaryBufferWriterBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset) { @@ -1745,7 +1551,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark { _order = order; OptionsPreset = optionsPreset; - _options = GetMemPackOptions(); + _options = BenchmarkOptions.GetMemPack(); _serialized = MemoryPackSerializer.Serialize(order, _options); // Serialize-side setup only — see AcBinaryBufferWriterBenchmark for the full rationale. @@ -1777,50 +1583,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark } } - internal sealed class SystemTextJsonBenchmark : ISerializerBenchmark - { - private readonly TestOrder _order; - private readonly JsonSerializerOptions _options; - private readonly string _serialized; - private readonly byte[] _serializedUtf8; - - public string Engine => Configuration.EngineSystemTextJson; - public string IoMode => Configuration.IoString; - public string DispatchMode => Configuration.ModeRuntime; // System.Text.Json default uses reflection-based metadata (no source generator opt-in here) - public string OptionsPreset { get; } - public int SerializedSize => _serializedUtf8.Length; - public long SetupSerializeAllocBytes => 0; - public long SetupDeserializeAllocBytes => 0; - - public SystemTextJsonBenchmark(TestOrder order, string optionsPreset) - { - _order = order; - OptionsPreset = optionsPreset; - _options = new JsonSerializerOptions - { - WriteIndented = false, - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, - ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles - }; - _serialized = JsonSerializer.Serialize(order, _options); - _serializedUtf8 = Configuration.Utf8NoBom.GetBytes(_serialized); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public void Serialize() => JsonSerializer.Serialize(_order, _options); - - [MethodImpl(MethodImplOptions.NoInlining)] - public void Deserialize() => JsonSerializer.Deserialize(_serialized, _options); - - public bool VerifyRoundTrip() - { - var json = JsonSerializer.Serialize(_order, _options); - var roundTripped = JsonSerializer.Deserialize(json, _options); - return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped); - } - } - - #endregion +#endregion // Results / output formatters → Output.cs // BenchmarkResult DTO → BenchmarkResult.cs