[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
|
public static class Program
|
||||||
{
|
{
|
||||||
// Configuration (constants, mutable state, attribute-flag aggregation) → Configuration.cs
|
// Configuration (constants, mutable state, attribute-flag aggregation) → Configuration.cs
|
||||||
|
// BuildAcBinary + GetMemPack helpers → Benchmarks/BenchmarkOptions.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
|
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
|
@ -637,141 +578,6 @@ private static List<BenchmarkResult> RunBenchmarksForTestData(TestDataSet testDa
|
||||||
|
|
||||||
#region Serializer Implementations
|
#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>
|
/// <summary>
|
||||||
/// Benchmarks AcBinary via the IBufferWriter overload with a pre-allocated, reused ArrayBufferWriter.
|
/// 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).
|
/// 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 int SerializedSize => _serialized.Length;
|
||||||
public long SetupSerializeAllocBytes => 0;
|
public long SetupSerializeAllocBytes => 0;
|
||||||
public long SetupDeserializeAllocBytes => 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)
|
public AcBinaryFreshBufferWriterBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset)
|
||||||
{
|
{
|
||||||
|
|
@ -896,7 +702,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
public long SetupSerializeAllocBytes { get; }
|
public long SetupSerializeAllocBytes { get; }
|
||||||
public long SetupDeserializeAllocBytes { get; }
|
public long SetupDeserializeAllocBytes { get; }
|
||||||
public bool IsRoundTripOnly => true;
|
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)
|
public AcBinaryNamedPipeBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset)
|
||||||
{
|
{
|
||||||
|
|
@ -1111,7 +917,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
public long SetupSerializeAllocBytes { get; }
|
public long SetupSerializeAllocBytes { get; }
|
||||||
public long SetupDeserializeAllocBytes { get; }
|
public long SetupDeserializeAllocBytes { get; }
|
||||||
public bool IsRoundTripOnly => true;
|
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)
|
public AcBinaryInMemoryPipeBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset)
|
||||||
{
|
{
|
||||||
|
|
@ -1297,7 +1103,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
public long SetupSerializeAllocBytes { get; }
|
public long SetupSerializeAllocBytes { get; }
|
||||||
public long SetupDeserializeAllocBytes { get; }
|
public long SetupDeserializeAllocBytes { get; }
|
||||||
public bool IsRoundTripOnly => true;
|
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)
|
public AcBinaryNamedPipeRawByteArrayBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset)
|
||||||
{
|
{
|
||||||
|
|
@ -1494,7 +1300,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
public long SetupSerializeAllocBytes { get; }
|
public long SetupSerializeAllocBytes { get; }
|
||||||
public long SetupDeserializeAllocBytes { get; }
|
public long SetupDeserializeAllocBytes { get; }
|
||||||
public bool IsRoundTripOnly => true;
|
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)
|
public AcBinaryInMemoryRawByteArrayBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset)
|
||||||
{
|
{
|
||||||
|
|
@ -1638,7 +1444,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
{
|
{
|
||||||
_order = order;
|
_order = order;
|
||||||
OptionsPreset = optionsPreset;
|
OptionsPreset = optionsPreset;
|
||||||
_options = GetMemPackOptions();
|
_options = BenchmarkOptions.GetMemPack();
|
||||||
_serialized = MemoryPackSerializer.Serialize(order, _options);
|
_serialized = MemoryPackSerializer.Serialize(order, _options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1677,7 +1483,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
public int SerializedSize => _serialized.Length;
|
public int SerializedSize => _serialized.Length;
|
||||||
public long SetupSerializeAllocBytes { get; }
|
public long SetupSerializeAllocBytes { get; }
|
||||||
public long SetupDeserializeAllocBytes => 0;
|
public long SetupDeserializeAllocBytes => 0;
|
||||||
public string OptionsDescription => BuildAcBinaryOptionsDescription(_options);
|
public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options);
|
||||||
|
|
||||||
public AcBinaryBufferWriterBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset)
|
public AcBinaryBufferWriterBenchmark(TestOrder order, AcBinarySerializerOptions options, string optionsPreset)
|
||||||
{
|
{
|
||||||
|
|
@ -1745,7 +1551,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
{
|
{
|
||||||
_order = order;
|
_order = order;
|
||||||
OptionsPreset = optionsPreset;
|
OptionsPreset = optionsPreset;
|
||||||
_options = GetMemPackOptions();
|
_options = BenchmarkOptions.GetMemPack();
|
||||||
_serialized = MemoryPackSerializer.Serialize(order, _options);
|
_serialized = MemoryPackSerializer.Serialize(order, _options);
|
||||||
|
|
||||||
// Serialize-side setup only — see AcBinaryBufferWriterBenchmark for the full rationale.
|
// Serialize-side setup only — see AcBinaryBufferWriterBenchmark for the full rationale.
|
||||||
|
|
@ -1777,50 +1583,7 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class SystemTextJsonBenchmark : ISerializerBenchmark
|
#endregion
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
||||||
// Results / output formatters → Output.cs
|
// Results / output formatters → Output.cs
|
||||||
// BenchmarkResult DTO → BenchmarkResult.cs
|
// BenchmarkResult DTO → BenchmarkResult.cs
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue