411 lines
16 KiB
C#
411 lines
16 KiB
C#
using AyCode.Core.Extensions;
|
|
using AyCode.Core.Tests.TestModels;
|
|
using BenchmarkDotNet.Attributes;
|
|
using BenchmarkDotNet.Jobs;
|
|
using MessagePack;
|
|
using MessagePack.Resolvers;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
|
|
|
namespace AyCode.Core.Benchmarks;
|
|
|
|
/// <summary>
|
|
/// Minimal benchmark to test if BenchmarkDotNet works without stack overflow.
|
|
/// </summary>
|
|
[ShortRunJob]
|
|
[MemoryDiagnoser]
|
|
public class MinimalBenchmark
|
|
{
|
|
private byte[] _data = null!;
|
|
private string _json = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
// Use very simple data - no circular references
|
|
var simpleData = new { Id = 1, Name = "Test", Value = 42.5 };
|
|
_json = System.Text.Json.JsonSerializer.Serialize(simpleData);
|
|
_data = Encoding.UTF8.GetBytes(_json);
|
|
Console.WriteLine($"Setup complete. Data size: {_data.Length} bytes");
|
|
}
|
|
|
|
[Benchmark]
|
|
public int GetLength() => _data.Length;
|
|
|
|
[Benchmark]
|
|
public string GetJson() => _json;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binary vs JSON benchmark with simple flat objects (no circular references).
|
|
/// </summary>
|
|
[ShortRunJob]
|
|
[MemoryDiagnoser]
|
|
public class SimpleBinaryBenchmark
|
|
{
|
|
private PrimitiveTestClass _testData = null!;
|
|
private byte[] _binaryData = null!;
|
|
private string _jsonData = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
_testData = TestDataFactory.CreatePrimitiveTestData();
|
|
_binaryData = AcBinarySerializer.Serialize(_testData);
|
|
_jsonData = AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
|
|
|
Console.WriteLine($"Binary: {_binaryData.Length} bytes, JSON: {_jsonData.Length} chars");
|
|
}
|
|
|
|
[Benchmark(Description = "Binary Serialize")]
|
|
public byte[] SerializeBinary() => AcBinarySerializer.Serialize(_testData);
|
|
|
|
[Benchmark(Description = "JSON Serialize", Baseline = true)]
|
|
public string SerializeJson() => AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
|
|
|
[Benchmark(Description = "Binary Deserialize")]
|
|
public PrimitiveTestClass? DeserializeBinary() => AcBinaryDeserializer.Deserialize<PrimitiveTestClass>(_binaryData);
|
|
|
|
[Benchmark(Description = "JSON Deserialize")]
|
|
public PrimitiveTestClass? DeserializeJson() => AcJsonDeserializer.Deserialize<PrimitiveTestClass>(_jsonData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Complex hierarchy benchmark - AcBinary vs JSON only (no MessagePack to isolate the issue).
|
|
/// </summary>
|
|
[ShortRunJob]
|
|
[MemoryDiagnoser]
|
|
[RankColumn]
|
|
public class ComplexBinaryBenchmark
|
|
{
|
|
private TestOrder _testOrder = null!;
|
|
private byte[] _acBinaryData = null!;
|
|
private string _jsonData = null!;
|
|
|
|
private AcBinarySerializerOptions _binaryOptions = null!;
|
|
private AcJsonSerializerOptions _jsonOptions = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
Console.WriteLine("Creating test data...");
|
|
_testOrder = TestDataFactory.CreateBenchmarkOrder(
|
|
itemCount: 2,
|
|
palletsPerItem: 2,
|
|
measurementsPerPallet: 2,
|
|
pointsPerMeasurement: 3);
|
|
Console.WriteLine($"Created order with {_testOrder.Items.Count} items");
|
|
|
|
_binaryOptions = AcBinarySerializerOptions.Default;
|
|
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
|
|
|
Console.WriteLine("Serializing AcBinary...");
|
|
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
|
Console.WriteLine($"AcBinary size: {_acBinaryData.Length} bytes");
|
|
|
|
Console.WriteLine("Serializing JSON...");
|
|
_jsonData = AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
|
Console.WriteLine($"JSON size: {_jsonData.Length} chars");
|
|
|
|
var jsonBytes = Encoding.UTF8.GetByteCount(_jsonData);
|
|
Console.WriteLine($"\n=== SIZE COMPARISON ===");
|
|
Console.WriteLine($"AcBinary: {_acBinaryData.Length,8:N0} bytes ({100.0 * _acBinaryData.Length / jsonBytes:F1}%)");
|
|
Console.WriteLine($"JSON: {jsonBytes,8:N0} bytes (100.0%)");
|
|
}
|
|
|
|
[Benchmark(Description = "AcBinary Serialize")]
|
|
public byte[] Serialize_AcBinary() => AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
|
|
|
[Benchmark(Description = "JSON Serialize", Baseline = true)]
|
|
public string Serialize_Json() => AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
|
|
|
[Benchmark(Description = "AcBinary Deserialize")]
|
|
public TestOrder? Deserialize_AcBinary() => AcBinaryDeserializer.Deserialize<TestOrder>(_acBinaryData);
|
|
|
|
[Benchmark(Description = "JSON Deserialize")]
|
|
public TestOrder? Deserialize_Json() => AcJsonDeserializer.Deserialize<TestOrder>(_jsonData, _jsonOptions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Full comparison with MessagePack - separate class to isolate potential issues.
|
|
/// </summary>
|
|
[ShortRunJob]
|
|
[MemoryDiagnoser]
|
|
[RankColumn]
|
|
public class MessagePackComparisonBenchmark
|
|
{
|
|
private TestOrder _testOrder = null!;
|
|
private byte[] _acBinaryData = null!;
|
|
private byte[] _msgPackData = null!;
|
|
private string _jsonData = null!;
|
|
|
|
private AcBinarySerializerOptions _binaryOptions = null!;
|
|
private MessagePackSerializerOptions _msgPackOptions = null!;
|
|
private AcJsonSerializerOptions _jsonOptions = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
Console.WriteLine("Creating test data...");
|
|
_testOrder = TestDataFactory.CreateBenchmarkOrder(
|
|
itemCount: 2,
|
|
palletsPerItem: 2,
|
|
measurementsPerPallet: 2,
|
|
pointsPerMeasurement: 3);
|
|
|
|
_binaryOptions = AcBinarySerializerOptions.Default;
|
|
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
|
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
|
|
|
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
|
_jsonData = AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
|
|
|
// MessagePack serialization in try-catch to see if it fails
|
|
try
|
|
{
|
|
Console.WriteLine("Serializing MessagePack...");
|
|
_msgPackData = MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
|
|
Console.WriteLine($"MessagePack size: {_msgPackData.Length} bytes");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"MessagePack serialization failed: {ex.Message}");
|
|
_msgPackData = Array.Empty<byte>();
|
|
}
|
|
|
|
var jsonBytes = Encoding.UTF8.GetByteCount(_jsonData);
|
|
Console.WriteLine($"\n=== SIZE COMPARISON ===");
|
|
Console.WriteLine($"AcBinary: {_acBinaryData.Length,8:N0} bytes ({100.0 * _acBinaryData.Length / jsonBytes:F1}%)");
|
|
Console.WriteLine($"MessagePack: {_msgPackData.Length,8:N0} bytes ({100.0 * _msgPackData.Length / jsonBytes:F1}%)");
|
|
Console.WriteLine($"JSON: {jsonBytes,8:N0} bytes (100.0%)");
|
|
}
|
|
|
|
[Benchmark(Description = "AcBinary Serialize")]
|
|
public byte[] Serialize_AcBinary() => AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
|
|
|
[Benchmark(Description = "MessagePack Serialize", Baseline = true)]
|
|
public byte[] Serialize_MsgPack() => MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
|
|
|
|
[Benchmark(Description = "AcBinary Deserialize")]
|
|
public TestOrder? Deserialize_AcBinary() => AcBinaryDeserializer.Deserialize<TestOrder>(_acBinaryData);
|
|
|
|
[Benchmark(Description = "MessagePack Deserialize")]
|
|
public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize<TestOrder>(_msgPackData, _msgPackOptions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Comprehensive AcBinary vs MessagePack comparison benchmark.
|
|
/// Tests: WithRef, NoRef, Populate, Serialize, Deserialize, Size
|
|
/// </summary>
|
|
[ShortRunJob]
|
|
[MemoryDiagnoser]
|
|
[RankColumn]
|
|
public class AcBinaryVsMessagePackFullBenchmark
|
|
{
|
|
// Test data
|
|
private TestOrder _testOrder = null!;
|
|
private TestOrder _populateTarget = null!;
|
|
|
|
// Serialized data - AcBinary
|
|
private byte[] _acBinaryWithRef = null!;
|
|
private byte[] _acBinaryNoRef = null!;
|
|
|
|
// Serialized data - MessagePack
|
|
private byte[] _msgPackData = null!;
|
|
|
|
// Options
|
|
private AcBinarySerializerOptions _withRefOptions = null!;
|
|
private AcBinarySerializerOptions _noRefOptions = null!;
|
|
private MessagePackSerializerOptions _msgPackOptions = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
// Create test data with shared references
|
|
TestDataFactory.ResetIdCounter();
|
|
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
|
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
|
var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
|
|
|
|
_testOrder = TestDataFactory.CreateOrder(
|
|
itemCount: 3,
|
|
palletsPerItem: 3,
|
|
measurementsPerPallet: 3,
|
|
pointsPerMeasurement: 4,
|
|
sharedTag: sharedTag,
|
|
sharedUser: sharedUser,
|
|
sharedMetadata: sharedMeta);
|
|
|
|
// Setup options
|
|
_withRefOptions = AcBinarySerializerOptions.Default; // WithRef by default
|
|
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
|
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
|
|
|
// Serialize with different options
|
|
_acBinaryWithRef = AcBinarySerializer.Serialize(_testOrder, _withRefOptions);
|
|
_acBinaryNoRef = AcBinarySerializer.Serialize(_testOrder, _noRefOptions);
|
|
_msgPackData = MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
|
|
|
|
// Create populate target
|
|
_populateTarget = new TestOrder { Id = _testOrder.Id };
|
|
foreach (var item in _testOrder.Items)
|
|
{
|
|
_populateTarget.Items.Add(new TestOrderItem { Id = item.Id });
|
|
}
|
|
|
|
// Print size comparison
|
|
PrintSizeComparison();
|
|
}
|
|
|
|
private void PrintSizeComparison()
|
|
{
|
|
Console.WriteLine("\n" + new string('=', 60));
|
|
Console.WriteLine("?? SIZE COMPARISON (AcBinary vs MessagePack)");
|
|
Console.WriteLine(new string('=', 60));
|
|
Console.WriteLine($" AcBinary WithRef: {_acBinaryWithRef.Length,8:N0} bytes");
|
|
Console.WriteLine($" AcBinary NoRef: {_acBinaryNoRef.Length,8:N0} bytes");
|
|
Console.WriteLine($" MessagePack: {_msgPackData.Length,8:N0} bytes");
|
|
Console.WriteLine(new string('-', 60));
|
|
Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryWithRef.Length / _msgPackData.Length:F1}% (WithRef)");
|
|
Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryNoRef.Length / _msgPackData.Length:F1}% (NoRef)");
|
|
Console.WriteLine(new string('=', 60) + "\n");
|
|
}
|
|
|
|
#region Serialize Benchmarks
|
|
|
|
[Benchmark(Description = "AcBinary Serialize WithRef")]
|
|
public byte[] Serialize_AcBinary_WithRef() => AcBinarySerializer.Serialize(_testOrder, _withRefOptions);
|
|
|
|
[Benchmark(Description = "AcBinary Serialize NoRef")]
|
|
public byte[] Serialize_AcBinary_NoRef() => AcBinarySerializer.Serialize(_testOrder, _noRefOptions);
|
|
|
|
[Benchmark(Description = "MessagePack Serialize", Baseline = true)]
|
|
public byte[] Serialize_MsgPack() => MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
|
|
|
|
#endregion
|
|
|
|
#region Deserialize Benchmarks
|
|
|
|
[Benchmark(Description = "AcBinary Deserialize WithRef")]
|
|
public TestOrder? Deserialize_AcBinary_WithRef() => AcBinaryDeserializer.Deserialize<TestOrder>(_acBinaryWithRef);
|
|
|
|
[Benchmark(Description = "AcBinary Deserialize NoRef")]
|
|
public TestOrder? Deserialize_AcBinary_NoRef() => AcBinaryDeserializer.Deserialize<TestOrder>(_acBinaryNoRef);
|
|
|
|
[Benchmark(Description = "MessagePack Deserialize")]
|
|
public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize<TestOrder>(_msgPackData, _msgPackOptions);
|
|
|
|
#endregion
|
|
|
|
#region Populate Benchmarks
|
|
|
|
[Benchmark(Description = "AcBinary Populate WithRef")]
|
|
public void Populate_AcBinary_WithRef()
|
|
{
|
|
var target = CreatePopulateTarget();
|
|
AcBinaryDeserializer.Populate(_acBinaryWithRef, target);
|
|
}
|
|
|
|
[Benchmark(Description = "AcBinary PopulateMerge WithRef")]
|
|
public void PopulateMerge_AcBinary_WithRef()
|
|
{
|
|
var target = CreatePopulateTarget();
|
|
AcBinaryDeserializer.PopulateMerge(_acBinaryWithRef.AsSpan(), target);
|
|
}
|
|
|
|
private TestOrder CreatePopulateTarget()
|
|
{
|
|
var target = new TestOrder { Id = _testOrder.Id };
|
|
foreach (var item in _testOrder.Items)
|
|
{
|
|
target.Items.Add(new TestOrderItem { Id = item.Id });
|
|
}
|
|
return target;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detailed size comparison - not a performance benchmark, just size output.
|
|
/// </summary>
|
|
[ShortRunJob]
|
|
[MemoryDiagnoser]
|
|
public class SizeComparisonBenchmark
|
|
{
|
|
private TestOrder _smallOrder = null!;
|
|
private TestOrder _mediumOrder = null!;
|
|
private TestOrder _largeOrder = null!;
|
|
|
|
private MessagePackSerializerOptions _msgPackOptions = null!;
|
|
private AcBinarySerializerOptions _withRefOptions = null!;
|
|
private AcBinarySerializerOptions _noRefOptions = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
|
_withRefOptions = AcBinarySerializerOptions.Default;
|
|
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
|
|
|
// Small order
|
|
TestDataFactory.ResetIdCounter();
|
|
_smallOrder = TestDataFactory.CreateOrder(itemCount: 1, palletsPerItem: 1, measurementsPerPallet: 1, pointsPerMeasurement: 2);
|
|
|
|
// Medium order
|
|
TestDataFactory.ResetIdCounter();
|
|
var sharedTag = TestDataFactory.CreateTag("Shared");
|
|
var sharedUser = TestDataFactory.CreateUser("shared");
|
|
_mediumOrder = TestDataFactory.CreateOrder(
|
|
itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 3,
|
|
sharedTag: sharedTag, sharedUser: sharedUser);
|
|
|
|
// Large order
|
|
TestDataFactory.ResetIdCounter();
|
|
sharedTag = TestDataFactory.CreateTag("SharedLarge");
|
|
sharedUser = TestDataFactory.CreateUser("sharedlarge");
|
|
var sharedMeta = TestDataFactory.CreateMetadata("meta", withChild: true);
|
|
_largeOrder = TestDataFactory.CreateOrder(
|
|
itemCount: 5, palletsPerItem: 4, measurementsPerPallet: 3, pointsPerMeasurement: 5,
|
|
sharedTag: sharedTag, sharedUser: sharedUser, sharedMetadata: sharedMeta);
|
|
|
|
PrintDetailedSizeComparison();
|
|
}
|
|
|
|
private void PrintDetailedSizeComparison()
|
|
{
|
|
Console.WriteLine("\n" + new string('=', 80));
|
|
Console.WriteLine("?? DETAILED SIZE COMPARISON: AcBinary vs MessagePack");
|
|
Console.WriteLine(new string('=', 80));
|
|
|
|
PrintOrderSize("Small Order (1x1x1x2)", _smallOrder);
|
|
PrintOrderSize("Medium Order (3x2x2x3) + SharedRefs", _mediumOrder);
|
|
PrintOrderSize("Large Order (5x4x3x5) + SharedRefs", _largeOrder);
|
|
|
|
Console.WriteLine(new string('=', 80) + "\n");
|
|
}
|
|
|
|
private void PrintOrderSize(string name, TestOrder order)
|
|
{
|
|
var acWithRef = AcBinarySerializer.Serialize(order, _withRefOptions);
|
|
var acNoRef = AcBinarySerializer.Serialize(order, _noRefOptions);
|
|
var msgPack = MessagePackSerializer.Serialize(order, _msgPackOptions);
|
|
|
|
Console.WriteLine($"\n {name}:");
|
|
Console.WriteLine($" AcBinary WithRef: {acWithRef.Length,8:N0} bytes ({100.0 * acWithRef.Length / msgPack.Length,5:F1}% of MsgPack)");
|
|
Console.WriteLine($" AcBinary NoRef: {acNoRef.Length,8:N0} bytes ({100.0 * acNoRef.Length / msgPack.Length,5:F1}% of MsgPack)");
|
|
Console.WriteLine($" MessagePack: {msgPack.Length,8:N0} bytes (100.0%)");
|
|
|
|
var withRefSaving = msgPack.Length - acWithRef.Length;
|
|
var noRefSaving = msgPack.Length - acNoRef.Length;
|
|
if (withRefSaving > 0)
|
|
Console.WriteLine($" ?? AcBinary WithRef saves: {withRefSaving:N0} bytes ({100.0 * withRefSaving / msgPack.Length:F1}%)");
|
|
else
|
|
Console.WriteLine($" ?? AcBinary WithRef larger by: {-withRefSaving:N0} bytes");
|
|
}
|
|
|
|
[Benchmark(Description = "Placeholder")]
|
|
public int Placeholder() => 1; // Just to make BenchmarkDotNet happy
|
|
} |