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); }