[LOADED_DOCS: 2 files, no new loads]
Refactor: extract serializer benchmarks to separate files Moved AcBinary, MemoryPack, MessagePack, and SystemTextJson benchmark classes into dedicated files for clarity. Centralized options formatting and MemoryPack selection logic in a new BenchmarkOptions helper. Updated Program.cs to use these helpers and removed redundant inline implementations, improving code organization without changing benchmark logic.
This commit is contained in:
parent
7fe21480e1
commit
bf42815ee5
|
|
@ -0,0 +1,46 @@
|
|||
using AyCode.Core.Serializers.Binaries;
|
||||
using AyCode.Core.Tests.TestModels;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AyCode.Core.Serializers.Console.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// AcBinary benchmark, Byte[] I/O mode. The headline AcBinary row in every cell — compared
|
||||
/// against <see cref="MemoryPackBenchmark"/> as the SOTA baseline.
|
||||
/// </summary>
|
||||
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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var bytes = AcBinarySerializer.Serialize(_order, _options);
|
||||
var roundTripped = AcBinaryDeserializer.Deserialize<TestOrder>(bytes, _options);
|
||||
return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using AyCode.Core.Serializers.Binaries;
|
||||
using MemoryPack;
|
||||
|
||||
namespace AyCode.Core.Serializers.Console.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <c>WireMode</c>-aligned options selection (so AcBinary FastWire ↔ MemoryPack
|
||||
/// UTF-16 comparisons stay apples-to-apples).
|
||||
/// </summary>
|
||||
internal static class BenchmarkOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 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. <c>Interning=All(opt) | False (attr)</c>) so attribute-suppressed features cannot
|
||||
/// silently mislead. Pass any benchmark-specific extras (e.g. <c>", BufferSize=4096B"</c>)
|
||||
/// in <paramref name="extra"/> — they are appended after the common fields.
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns MemoryPack serializer options aligned with <see cref="Configuration.SelectedWireMode"/> for a fair
|
||||
/// apples-to-apples wire-format comparison:
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="WireMode.Compact"/> → <see cref="MemoryPackSerializerOptions.Default"/> (UTF-8) — both
|
||||
/// engines encode UTF-8, comparison is purely about header / tier / dispatch overhead.</item>
|
||||
/// <item><see cref="WireMode.Fast"/> → <see cref="MemoryPackSerializerOptions.Utf16"/> (UTF-16 raw memcpy) —
|
||||
/// both engines write UTF-16 raw bytes, so wire-size and CPU comparison reflect the same string-encoding family.</item>
|
||||
/// </list>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static MemoryPackSerializerOptions GetMemPack() =>
|
||||
Configuration.SelectedWireMode == WireMode.Fast
|
||||
? MemoryPackSerializerOptions.Utf16
|
||||
: MemoryPackSerializerOptions.Default;
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
using AyCode.Core.Tests.TestModels;
|
||||
using MemoryPack;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AyCode.Core.Serializers.Console.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// MemoryPack benchmark, Byte[] I/O mode. The SOTA baseline AcBinary is compared against in every
|
||||
/// cell. WireMode-aligned options via <see cref="BenchmarkOptions.GetMemPack"/> so Compact ↔ UTF-8
|
||||
/// and FastWire ↔ UTF-16 are apples-to-apples on the string-encoding axis.
|
||||
/// </summary>
|
||||
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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var bytes = MemoryPackSerializer.Serialize(_order, _options);
|
||||
var roundTripped = MemoryPackSerializer.Deserialize<TestOrder>(bytes, _options);
|
||||
return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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 (<c>dotnet run</c>) only.
|
||||
/// </summary>
|
||||
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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var bytes = MessagePackSerializer.Serialize(_order, _options);
|
||||
var roundTripped = MessagePackSerializer.Deserialize<TestOrder>(bytes, _options);
|
||||
return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using AyCode.Core.Tests.TestModels;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AyCode.Core.Serializers.Console.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <c>BenchmarkLoop.CreateSerializers</c>); ranks far behind binary serializers on µs/op but provides
|
||||
/// a familiar JSON baseline when needed.
|
||||
/// </summary>
|
||||
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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var json = JsonSerializer.Serialize(_order, _options);
|
||||
var roundTripped = JsonSerializer.Deserialize<TestOrder>(json, _options);
|
||||
return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped);
|
||||
}
|
||||
}
|
||||
|
|
@ -35,66 +35,7 @@ namespace AyCode.Core.Serializers.Console;
|
|||
public static class Program
|
||||
{
|
||||
// Configuration (constants, mutable state, attribute-flag aggregation) → Configuration.cs
|
||||
|
||||
/// <summary>
|
||||
/// 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. <c>Interning=All(opt) | False (attr)</c>) so attribute-suppressed features cannot
|
||||
/// silently mislead. Pass any benchmark-specific extras (e.g. <c>", BufferSize=4096B"</c>)
|
||||
/// in <paramref name="extra"/> — they are appended after the common fields.
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns MemoryPack serializer options aligned with <see cref="Configuration.SelectedWireMode"/> for a fair
|
||||
/// apples-to-apples wire-format comparison:
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="WireMode.Compact"/> → <see cref="MemoryPackSerializerOptions.Default"/> (UTF-8) — both
|
||||
/// engines encode UTF-8, comparison is purely about header / tier / dispatch overhead.</item>
|
||||
/// <item><see cref="WireMode.Fast"/> → <see cref="MemoryPackSerializerOptions.Utf16"/> (UTF-16 raw memcpy) —
|
||||
/// both engines write UTF-16 raw bytes, so wire-size and CPU comparison reflect the same string-encoding family.</item>
|
||||
/// </list>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static MemoryPackSerializerOptions GetMemPackOptions() =>
|
||||
Configuration.SelectedWireMode == WireMode.Fast
|
||||
? MemoryPackSerializerOptions.Utf16
|
||||
: MemoryPackSerializerOptions.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a total-time (in ms across <see cref="Configuration.TestIterations"/>) into per-operation microseconds.
|
||||
/// Formula: <c>totalMs / iterations × 1000</c>. The benchmark stores <c>*TimeMs</c> as the cumulative
|
||||
/// median over the timing run; the display layer renders per-op µs to make numbers iteration-count
|
||||
/// independent (e.g. switching <c>Configuration.TestIterations</c> 1000 → 100 leaves the displayed µs/op unchanged
|
||||
/// — only its sample noise grows). Symmetric with the already-per-op <c>*AllocBytesPerOp</c> fields.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
/// <summary>
|
||||
/// Converts a total-time (in ms across <paramref name="iterations"/>) 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
|
||||
/// <c>iterations</c> a per-row property — there is no longer a single global Configuration.TestIterations to divide by.
|
||||
/// </summary>
|
||||
// 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<BenchmarkResult> 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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var bytes = AcBinarySerializer.Serialize(_order, _options);
|
||||
var roundTripped = AcBinaryDeserializer.Deserialize<TestOrder>(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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var bytes = MemoryPackSerializer.Serialize(_order, _options);
|
||||
var roundTripped = MemoryPackSerializer.Deserialize<TestOrder>(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<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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var bytes = MessagePackSerializer.Serialize(_order, _options);
|
||||
var roundTripped = MessagePackSerializer.Deserialize<TestOrder>(bytes, _options);
|
||||
return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 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<TestOrder>(_serialized, _options);
|
||||
|
||||
public bool VerifyRoundTrip()
|
||||
{
|
||||
var json = JsonSerializer.Serialize(_order, _options);
|
||||
var roundTripped = JsonSerializer.Deserialize<TestOrder>(json, _options);
|
||||
return BenchmarkLoop.DeepEqualsViaJson(_order, roundTripped);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
// Results / output formatters → Output.cs
|
||||
// BenchmarkResult DTO → BenchmarkResult.cs
|
||||
|
|
|
|||
Loading…
Reference in New Issue