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; /// /// 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); _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(_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). /// [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(_acBinaryData); [Benchmark(Description = "JSON Deserialize")] public TestOrder? Deserialize_Json() => AcJsonDeserializer.Deserialize(_jsonData, _jsonOptions); } /// /// Full comparison with MessagePack - separate class to isolate potential issues. /// [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(); } 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(_acBinaryData); [Benchmark(Description = "MessagePack Deserialize")] public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize(_msgPackData, _msgPackOptions); } /// /// Comprehensive AcBinary vs MessagePack comparison benchmark. /// Tests: WithRef, NoRef, 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!; // 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(_acBinaryWithRef); [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); #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 } /// /// Detailed size comparison - not a performance benchmark, just size output. /// [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 }