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;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using System.IO;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
namespace AyCode.Core.Benchmarks;
///
/// Minimal benchmark to test if BenchmarkDotNet works without stack overflow.
///
[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;
}
///
/// Binary vs JSON benchmark with simple flat objects (no circular references).
///
[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, AcBinarySerializerOptions.WithoutReferenceHandling());
_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, AcBinarySerializerOptions.WithoutReferenceHandling());
[Benchmark(Description = "JSON Serialize", Baseline = true)]
public string SerializeJson() => AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
[Benchmark(Description = "Binary Deserialize")]
public PrimitiveTestClass? DeserializeBinary() => AcBinaryDeserializer.Deserialize(_binaryData);
[Benchmark(Description = "JSON Deserialize")]
public PrimitiveTestClass? DeserializeJson() => AcJsonDeserializer.Deserialize(_jsonData, AcJsonSerializerOptions.WithoutReferenceHandling());
}
///
/// Complex hierarchy benchmark - AcBinary vs JSON only (no MessagePack to isolate the issue).
/// Uses AcBinary without reference handling.
///
[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.WithoutReferenceHandling();
_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(_acBinaryData);
[Benchmark(Description = "JSON Deserialize")]
public TestOrder? Deserialize_Json() => AcJsonDeserializer.Deserialize(_jsonData, _jsonOptions);
}
///
/// Full comparison with MessagePack and BSON - AcBinary uses NO reference handling everywhere.
///
[ShortRunJob]
[MemoryDiagnoser]
[RankColumn]
public class MessagePackComparisonBenchmark
{
private TestOrder _testOrder = null!;
private byte[] _acBinaryData = null!;
private byte[] _msgPackData = null!;
private byte[] _bsonData = 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.WithoutReferenceHandling();
_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();
}
// BSON serialization
try
{
Console.WriteLine("Serializing BSON...");
var bsonDoc = _testOrder.ToBsonDocument();
_bsonData = bsonDoc.ToBson();
Console.WriteLine($"BSON size: {_bsonData.Length} bytes");
}
catch (Exception ex)
{
Console.WriteLine($"BSON serialization failed: {ex.Message}");
_bsonData = Array.Empty();
}
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($"BSON: {_bsonData.Length,8:N0} bytes ({100.0 * _bsonData.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 = "BSON Serialize")]
public byte[] Serialize_Bson() => _testOrder.ToBsonDocument().ToBson();
[Benchmark(Description = "AcBinary Deserialize")]
public TestOrder? Deserialize_AcBinary() => AcBinaryDeserializer.Deserialize(_acBinaryData);
[Benchmark(Description = "MessagePack Deserialize")]
public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize(_msgPackData, _msgPackOptions);
[Benchmark(Description = "BSON Deserialize")]
public TestOrder? Deserialize_Bson()
{
if (_bsonData == null || _bsonData.Length == 0) return null;
using var ms = new MemoryStream(_bsonData);
using var reader = new BsonBinaryReader(ms);
return BsonSerializer.Deserialize(reader);
}
}
///
/// Comprehensive AcBinary vs MessagePack comparison benchmark.
/// Tests: NoRef (everywhere), Populate, Serialize, Deserialize, Size
///
[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!;
private byte[] _bsonData = 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 - enforce no reference handling everywhere
_withRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
_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);
// BSON
try
{
_bsonData = _testOrder.ToBsonDocument().ToBson();
}
catch
{
_bsonData = Array.Empty();
}
// 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 vs BSON)");
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($" BSON: {_bsonData.Length,8:N0} bytes");
Console.WriteLine(new string('-', 60));
Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryWithRef.Length / Math.Max(1, _msgPackData.Length):F1}% (WithRef - actually NoRef)");
Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryNoRef.Length / Math.Max(1, _msgPackData.Length):F1}% (NoRef)");
Console.WriteLine(new string('=', 60) + "\n");
}
#region Serialize Benchmarks
[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);
[Benchmark(Description = "BSON Serialize")]
public byte[] Serialize_Bson() => _testOrder.ToBsonDocument().ToBson();
#endregion
#region Deserialize Benchmarks
[Benchmark(Description = "AcBinary Deserialize NoRef")]
public TestOrder? Deserialize_AcBinary_NoRef() => AcBinaryDeserializer.Deserialize(_acBinaryNoRef);
[Benchmark(Description = "MessagePack Deserialize")]
public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize(_msgPackData, _msgPackOptions);
[Benchmark(Description = "BSON Deserialize")]
public TestOrder? Deserialize_Bson()
{
if (_bsonData == null || _bsonData.Length == 0) return null;
using var ms = new MemoryStream(_bsonData);
using var reader = new BsonBinaryReader(ms);
return BsonSerializer.Deserialize(reader);
}
#endregion
#region Populate Benchmarks
[Benchmark(Description = "AcBinary Populate NoRef")]
public void Populate_AcBinary_NoRef()
{
// Create fresh target each time to avoid state accumulation
var target = CreatePopulateTarget();
AcBinaryDeserializer.Populate(_acBinaryNoRef, target);
}
[Benchmark(Description = "AcBinary PopulateMerge NoRef")]
public void PopulateMerge_AcBinary_NoRef()
{
// Create fresh target each time to avoid state accumulation
var target = CreatePopulateTarget();
AcBinaryDeserializer.PopulateMerge(_acBinaryNoRef.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
}
///
/// Detailed size comparison - not a performance benchmark, just size output.
/// Now includes BSON size output and uses AcBinary without reference handling.
///
[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.WithoutReferenceHandling();
_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 vs BSON");
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);
byte[] bson;
try { bson = order.ToBsonDocument().ToBson(); } catch { bson = Array.Empty(); }
Console.WriteLine($"\n {name}:");
Console.WriteLine($" AcBinary WithRef: {acWithRef.Length,8:N0} bytes ({100.0 * acWithRef.Length / Math.Max(1, msgPack.Length),5:F1}% of MsgPack)");
Console.WriteLine($" AcBinary NoRef: {acNoRef.Length,8:N0} bytes ({100.0 * acNoRef.Length / Math.Max(1, msgPack.Length),5:F1}% of MsgPack)");
Console.WriteLine($" MessagePack: {msgPack.Length,8:N0} bytes (100.0%)");
Console.WriteLine($" BSON: {bson.Length,8:N0} bytes (compared to MsgPack)");
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
}
public enum BinaryBenchmarkMode
{
Default,
NoReferenceHandling,
FastMode
}
public abstract class AcBinaryOptionsBenchmarkBase
{
protected TestOrder TestOrder = null!;
protected AcBinarySerializerOptions BinaryOptions = null!;
protected MessagePackSerializerOptions MsgPackOptions = null!;
protected byte[] AcBinaryData = null!;
protected byte[] MsgPackData = null!;
[Params(BinaryBenchmarkMode.Default, BinaryBenchmarkMode.NoReferenceHandling, BinaryBenchmarkMode.FastMode)]
public BinaryBenchmarkMode Mode { get; set; }
[GlobalSetup]
public void GlobalSetup()
{
TestDataFactory.ResetIdCounter();
TestOrder = TestDataFactory.CreateBenchmarkOrder(
itemCount: 4,
palletsPerItem: 3,
measurementsPerPallet: 3,
pointsPerMeasurement: 6);
BinaryOptions = CreateBinaryOptions(Mode);
MsgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
AcBinaryData = AcBinarySerializer.Serialize(TestOrder, BinaryOptions);
MsgPackData = MessagePackSerializer.Serialize(TestOrder, MsgPackOptions);
var ratio = MsgPackData.Length == 0 ? 0 : 100.0 * AcBinaryData.Length / MsgPackData.Length;
Console.WriteLine($"[BenchmarkSetup] Mode={Mode} | AcBinary={AcBinaryData.Length} bytes | MessagePack={MsgPackData.Length} bytes | Ratio={ratio:F1}%");
}
private static AcBinarySerializerOptions CreateBinaryOptions(BinaryBenchmarkMode mode) => mode switch
{
BinaryBenchmarkMode.Default => new AcBinarySerializerOptions(),
BinaryBenchmarkMode.NoReferenceHandling => AcBinarySerializerOptions.WithoutReferenceHandling(),
BinaryBenchmarkMode.FastMode => new AcBinarySerializerOptions
{
UseMetadata = false,
UseStringInterning = false,
UseReferenceHandling = false
},
_ => new AcBinarySerializerOptions()
};
}
[ShortRunJob]
[MemoryDiagnoser]
[RankColumn]
public class AcBinaryOptionsSerializeBenchmark : AcBinaryOptionsBenchmarkBase
{
[Benchmark(Description = "MessagePack Serialize", Baseline = true)]
public byte[] Serialize_MessagePack() => MessagePackSerializer.Serialize(TestOrder, MsgPackOptions);
[Benchmark(Description = "AcBinary Serialize")]
public byte[] Serialize_AcBinary() => AcBinarySerializer.Serialize(TestOrder, BinaryOptions);
}
[ShortRunJob]
[MemoryDiagnoser]
[RankColumn]
public class AcBinaryOptionsDeserializeBenchmark : AcBinaryOptionsBenchmarkBase
{
[Benchmark(Description = "MessagePack Deserialize", Baseline = true)]
public TestOrder? Deserialize_MessagePack() => MessagePackSerializer.Deserialize(MsgPackData, MsgPackOptions);
[Benchmark(Description = "AcBinary Deserialize")]
public TestOrder? Deserialize_AcBinary() => AcBinaryDeserializer.Deserialize(AcBinaryData);
}