using System.Diagnostics; using AyCode.Core.Extensions; using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Jsons; using AyCode.Core.Tests.TestModels; using MessagePack; using MessagePack.Resolvers; namespace AyCode.Core.Tests.serialization; [TestClass] public class QuickBenchmark { private static readonly MessagePackSerializerOptions MsgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); private const int DefaultIterations = 1000; #region Helper Methods private static void PrintBanner(string title) { Console.WriteLine(); Console.WriteLine(new string('=', 78)); Console.WriteLine(title); Console.WriteLine(new string('=', 78)); } private static void PrintTableHeader(string title) { PrintBanner(title); Console.WriteLine($"{"Metric",-25} | {"AcBinary",14} | {"MessagePack",14} | {"Ratio",14}"); Console.WriteLine(new string('-', 78)); } private static void PrintTableRow(string metric, double acBinary, double msgPack, string unit = "ms") { if (msgPack > 0) { var ratio = acBinary / msgPack; var ratioStr = ratio < 1 ? $"{ratio:F2}x faster" : $"{ratio:F2}x slower"; Console.WriteLine($"{metric,-25} | {acBinary,10:F2} {unit,-2} | {msgPack,10:F2} {unit,-2} | {ratioStr,14}"); } else { Console.WriteLine($"{metric,-25} | {acBinary,10:F2} {unit,-2} | {"N/A",12} | {"(unique)",14}"); } } private static void PrintTableRowSize(string metric, int acBinary, int msgPack) { var ratio = msgPack == 0 ? 0 : 100.0 * acBinary / msgPack; Console.WriteLine($"{metric,-25} | {acBinary,14:N0} | {msgPack,14:N0} | {ratio,12:F1}%"); } private static void PrintTableFooter() { Console.WriteLine(new string('-', 78)); } private static void PrintSummary(int acBinarySize, int msgPackSize, double acSerMs, double msgSerMs, double acDeserMs, double msgDeserMs) { PrintBanner("SUMMARY"); var sizeAdvantage = msgPackSize == 0 ? 0 : 100.0 - (100.0 * acBinarySize / msgPackSize); if (sizeAdvantage > 0) Console.WriteLine($"[OK] Size: AcBinary is {sizeAdvantage:F1}% smaller ({msgPackSize - acBinarySize:N0} bytes saved)"); else Console.WriteLine($"[WARN] Size: AcBinary is {-sizeAdvantage:F1}% larger"); var serRatio = msgSerMs == 0 ? 0 : acSerMs / msgSerMs; if (serRatio > 0 && serRatio < 1) Console.WriteLine($"[OK] Serialize: AcBinary is {1 / serRatio:F2}x faster"); else if (serRatio > 0) Console.WriteLine($"[WARN] Serialize: AcBinary is {serRatio:F2}x slower"); var deserRatio = msgDeserMs == 0 ? 0 : acDeserMs / msgDeserMs; if (deserRatio > 0 && deserRatio < 1) Console.WriteLine($"[OK] Deserialize: AcBinary is {1 / deserRatio:F2}x faster"); else if (deserRatio > 0) Console.WriteLine($"[WARN] Deserialize: AcBinary is {deserRatio:F2}x slower"); } private static TestOrder CreatePopulateTarget(TestOrder source) { var target = new TestOrder { Id = source.Id }; foreach (var item in source.Items) { target.Items.Add(new TestOrderItem { Id = item.Id }); } return target; } #endregion #region Basic Benchmarks [TestMethod] public void RunQuickBenchmark() { // Warmup var order = TestDataFactory.CreateBenchmarkOrder(2, 2, 2, 3); for (int i = 0; i < 10; i++) { var bytes = order.ToBinary(); var result = bytes.BinaryTo(); } // Measure serialize const int iterations = DefaultIterations; var sw = Stopwatch.StartNew(); byte[] serialized = null!; for (int i = 0; i < iterations; i++) { serialized = order.ToBinary(); } sw.Stop(); var serializeMs = sw.Elapsed.TotalMilliseconds; // Measure deserialize sw.Restart(); TestOrder? deserialized = null; for (int i = 0; i < iterations; i++) { deserialized = serialized.BinaryTo(); } sw.Stop(); var deserializeMs = sw.Elapsed.TotalMilliseconds; // JSON comparison var jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling(); sw.Restart(); string json = null!; for (int i = 0; i < iterations; i++) { json = order.ToJson(jsonOptions); } sw.Stop(); var jsonSerializeMs = sw.Elapsed.TotalMilliseconds; sw.Restart(); for (int i = 0; i < iterations; i++) { var _ = json.JsonTo(jsonOptions); } sw.Stop(); var jsonDeserializeMs = sw.Elapsed.TotalMilliseconds; Console.WriteLine($"=== Quick Benchmark ({iterations} iterations) ==="); Console.WriteLine($"Binary size: {serialized.Length} bytes"); Console.WriteLine($"JSON size: {json.Length} chars ({System.Text.Encoding.UTF8.GetByteCount(json)} bytes)"); Console.WriteLine(); Console.WriteLine($"Binary Serialize: {serializeMs:F2}ms ({serializeMs / iterations:F4}ms/op)"); Console.WriteLine($"Binary Deserialize: {deserializeMs:F2}ms ({deserializeMs / iterations:F4}ms/op)"); Console.WriteLine($"JSON Serialize: {jsonSerializeMs:F2}ms ({jsonSerializeMs / iterations:F4}ms/op)"); Console.WriteLine($"JSON Deserialize: {jsonDeserializeMs:F2}ms ({jsonDeserializeMs / iterations:F4}ms/op)"); Console.WriteLine(); Console.WriteLine($"Binary vs JSON Serialize: {serializeMs / jsonSerializeMs:F2}x"); Console.WriteLine($"Binary vs JSON Deserialize: {deserializeMs / jsonDeserializeMs:F2}x"); Console.WriteLine($"Size ratio: {100.0 * serialized.Length / System.Text.Encoding.UTF8.GetByteCount(json):F1}%"); Assert.IsNotNull(deserialized); Assert.AreEqual(order.Id, deserialized.Id); } [TestMethod] public void RunStringInterningBenchmark() { // Create data with repeated strings var items = Enumerable.Range(0, 100).Select(i => new TestClassWithRepeatedValues { Id = i, Status = i % 3 == 0 ? "Pending" : i % 3 == 1 ? "Processing" : "Completed", Category = $"Category_{i % 5}", Priority = i % 2 == 0 ? "High_Priority" : "Low_Priority" }).ToList(); // Warmup for (int i = 0; i < 10; i++) { var bytes = items.ToBinary(); var result = bytes.BinaryTo>(); } const int iterations = DefaultIterations; // With interning (default) var sw = Stopwatch.StartNew(); byte[] withInterning = null!; for (int i = 0; i < iterations; i++) { withInterning = AcBinarySerializer.Serialize(items, AcBinarySerializerOptions.Default); } sw.Stop(); var withInterningMs = sw.Elapsed.TotalMilliseconds; // Without interning var noInternOptions = new AcBinarySerializerOptions { UseStringInterning = false }; sw.Restart(); byte[] withoutInterning = null!; for (int i = 0; i < iterations; i++) { withoutInterning = AcBinarySerializer.Serialize(items, noInternOptions); } sw.Stop(); var withoutInterningMs = sw.Elapsed.TotalMilliseconds; Console.WriteLine($"=== String Interning Benchmark ({iterations} iterations) ==="); Console.WriteLine($"With interning: {withInterning.Length} bytes, {withInterningMs:F2}ms"); Console.WriteLine($"Without interning: {withoutInterning.Length} bytes, {withoutInterningMs:F2}ms"); Console.WriteLine($"Size savings: {withoutInterning.Length - withInterning.Length} bytes ({100.0 * (withoutInterning.Length - withInterning.Length) / withoutInterning.Length:F1}%)"); Console.WriteLine($"Speed ratio: {withInterningMs / withoutInterningMs:F2}x"); // Verify both deserialize correctly var result1 = withInterning.BinaryTo>(); var result2 = withoutInterning.BinaryTo>(); Assert.AreEqual(100, result1!.Count); Assert.AreEqual(100, result2!.Count); } #endregion #region MessagePack Comparison [TestMethod] public void RunMessagePackComparison() { // Create test data var order = TestDataFactory.CreateBenchmarkOrder(3, 3, 3, 4); // Warmup for (int i = 0; i < 20; i++) { var binBytes = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default); var binResult = AcBinaryDeserializer.Deserialize(binBytes); var msgBytes = MessagePackSerializer.Serialize(order, MsgPackOptions); var msgResult = MessagePackSerializer.Deserialize(msgBytes, MsgPackOptions); } const int iterations = DefaultIterations; // === AcBinary Serialize === var sw = Stopwatch.StartNew(); byte[] acBinaryData = null!; for (int i = 0; i < iterations; i++) { acBinaryData = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default); } sw.Stop(); var acBinarySerMs = sw.Elapsed.TotalMilliseconds; // === MessagePack Serialize === sw.Restart(); byte[] msgPackData = null!; for (int i = 0; i < iterations; i++) { msgPackData = MessagePackSerializer.Serialize(order, MsgPackOptions); } sw.Stop(); var msgPackSerMs = sw.Elapsed.TotalMilliseconds; // === AcBinary Deserialize === sw.Restart(); TestOrder? acBinaryResult = null; for (int i = 0; i < iterations; i++) { acBinaryResult = AcBinaryDeserializer.Deserialize(acBinaryData); } sw.Stop(); var acBinaryDeserMs = sw.Elapsed.TotalMilliseconds; // === MessagePack Deserialize === sw.Restart(); TestOrder? msgPackResult = null; for (int i = 0; i < iterations; i++) { msgPackResult = MessagePackSerializer.Deserialize(msgPackData, MsgPackOptions); } sw.Stop(); var msgPackDeserMs = sw.Elapsed.TotalMilliseconds; // Print results Console.WriteLine($"=== AcBinary vs MessagePack Benchmark ({iterations} iterations) ==="); Console.WriteLine(); Console.WriteLine($"{"Metric",-25} {"AcBinary",12} {"MessagePack",12} {"Ratio",10}"); Console.WriteLine(new string('-', 60)); Console.WriteLine($"{"Size (bytes)",-25} {acBinaryData.Length,12:N0} {msgPackData.Length,12:N0} {100.0 * acBinaryData.Length / msgPackData.Length,9:F1}%"); Console.WriteLine($"{"Serialize (ms)",-25} {acBinarySerMs,12:F2} {msgPackSerMs,12:F2} {acBinarySerMs / msgPackSerMs,9:F2}x"); Console.WriteLine($"{"Deserialize (ms)",-25} {acBinaryDeserMs,12:F2} {msgPackDeserMs,12:F2} {acBinaryDeserMs / msgPackDeserMs,9:F2}x"); Console.WriteLine($"{"Round-trip (ms)",-25} {acBinarySerMs + acBinaryDeserMs,12:F2} {msgPackSerMs + msgPackDeserMs,12:F2} {(acBinarySerMs + acBinaryDeserMs) / (msgPackSerMs + msgPackDeserMs),9:F2}x"); Console.WriteLine(); var sizeDiff = msgPackData.Length - acBinaryData.Length; if (sizeDiff > 0) Console.WriteLine($"[OK] AcBinary {sizeDiff:N0} bytes smaller ({100.0 * sizeDiff / msgPackData.Length:F1}% savings)"); else Console.WriteLine($"[WARN] AcBinary {-sizeDiff:N0} bytes larger"); Assert.IsNotNull(acBinaryResult); Assert.IsNotNull(msgPackResult); Assert.AreEqual(order.Id, acBinaryResult.Id); } [TestMethod] public void RunStringInterningVsMessagePack() { // Create data with many repeated strings (worst case for MessagePack, best for interning) var items = Enumerable.Range(0, 200).Select(i => new TestClassWithRepeatedValues { Id = i, Status = i % 3 == 0 ? "PendingStatus" : i % 3 == 1 ? "ProcessingStatus" : "CompletedStatus", Category = $"Category_{i % 5}", Priority = i % 2 == 0 ? "HighPriority" : "LowPriority" }).ToList(); // Warmup for (int i = 0; i < 20; i++) { var b1 = AcBinarySerializer.Serialize(items, AcBinarySerializerOptions.Default); var r1 = AcBinaryDeserializer.Deserialize>(b1); var b2 = MessagePackSerializer.Serialize(items, MsgPackOptions); var r2 = MessagePackSerializer.Deserialize>(b2, MsgPackOptions); } const int iterations = DefaultIterations; // AcBinary with interning var sw = Stopwatch.StartNew(); byte[] acWithIntern = null!; for (int i = 0; i < iterations; i++) { acWithIntern = AcBinarySerializer.Serialize(items, AcBinarySerializerOptions.Default); } var acSerMs = sw.Elapsed.TotalMilliseconds; sw.Restart(); for (int i = 0; i < iterations; i++) { var _ = AcBinaryDeserializer.Deserialize>(acWithIntern); } var acDeserMs = sw.Elapsed.TotalMilliseconds; // MessagePack sw.Restart(); byte[] msgPack = null!; for (int i = 0; i < iterations; i++) { msgPack = MessagePackSerializer.Serialize(items, MsgPackOptions); } var msgSerMs = sw.Elapsed.TotalMilliseconds; sw.Restart(); for (int i = 0; i < iterations; i++) { var _ = MessagePackSerializer.Deserialize>(msgPack, MsgPackOptions); } var msgDeserMs = sw.Elapsed.TotalMilliseconds; Console.WriteLine($"=== String Interning Advantage ({iterations} iterations, 200 items with repeated strings) ==="); Console.WriteLine(); Console.WriteLine($"{"Metric",-25} {"AcBinary",12} {"MessagePack",12} {"Ratio",10}"); Console.WriteLine(new string('-', 60)); Console.WriteLine($"{"Size (bytes)",-25} {acWithIntern.Length,12:N0} {msgPack.Length,12:N0} {100.0 * acWithIntern.Length / msgPack.Length,9:F1}%"); Console.WriteLine($"{"Serialize (ms)",-25} {acSerMs,12:F2} {msgSerMs,12:F2} {acSerMs / msgSerMs,9:F2}x"); Console.WriteLine($"{"Deserialize (ms)",-25} {acDeserMs,12:F2} {msgDeserMs,12:F2} {acDeserMs / msgDeserMs,9:F2}x"); Console.WriteLine(); var sizeSaving = msgPack.Length - acWithIntern.Length; Console.WriteLine($"[OK] String interning saves {sizeSaving:N0} bytes ({100.0 * sizeSaving / msgPack.Length:F1}%)"); Assert.IsTrue(acWithIntern.Length < msgPack.Length, "AcBinary with interning should be smaller"); } #endregion #region Full Comparison (WithRef, NoRef, Populate, Merge) [TestMethod] public void RunFullBenchmarkComparison() { PrintBanner("AcBinary vs MessagePack Full Benchmark"); // 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); var testOrder = TestDataFactory.CreateOrder( itemCount: 3, palletsPerItem: 3, measurementsPerPallet: 3, pointsPerMeasurement: 4, sharedTag: sharedTag, sharedUser: sharedUser, sharedMetadata: sharedMeta); // Options var withRefOptions = new AcBinarySerializerOptions(); var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling(); // Warmup Console.WriteLine("\nWarming up..."); for (int i = 0; i < 100; i++) { _ = AcBinarySerializer.Serialize(testOrder, withRefOptions); _ = AcBinarySerializer.Serialize(testOrder, noRefOptions); _ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions); } // Pre-serialize var acBinaryWithRef = AcBinarySerializer.Serialize(testOrder, withRefOptions); var acBinaryNoRef = AcBinarySerializer.Serialize(testOrder, noRefOptions); var msgPackData = MessagePackSerializer.Serialize(testOrder, MsgPackOptions); Console.WriteLine($"Iterations: {DefaultIterations:N0}"); // Size comparison PrintTableHeader("SIZE COMPARISON"); PrintTableRowSize("AcBinary (WithRef)", acBinaryWithRef.Length, msgPackData.Length); PrintTableRowSize("AcBinary (NoRef)", acBinaryNoRef.Length, msgPackData.Length); PrintTableRowSize("MessagePack (baseline)", msgPackData.Length, msgPackData.Length); PrintTableFooter(); var sw = Stopwatch.StartNew(); // === Serialize WithRef === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinarySerializer.Serialize(testOrder, withRefOptions); var acWithRefSerMs = sw.Elapsed.TotalMilliseconds; // === Serialize NoRef === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinarySerializer.Serialize(testOrder, noRefOptions); var acNoRefSerMs = sw.Elapsed.TotalMilliseconds; // === MessagePack Serialize === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions); var msgPackSerMs = sw.Elapsed.TotalMilliseconds; // === Deserialize WithRef === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinaryDeserializer.Deserialize(acBinaryWithRef); var acWithRefDeserMs = sw.Elapsed.TotalMilliseconds; // === Deserialize NoRef === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinaryDeserializer.Deserialize(acBinaryNoRef); var acNoRefDeserMs = sw.Elapsed.TotalMilliseconds; // === MessagePack Deserialize === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = MessagePackSerializer.Deserialize(msgPackData, MsgPackOptions); var msgPackDeserMs = sw.Elapsed.TotalMilliseconds; // === Populate (AcBinary only) === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) { var target = CreatePopulateTarget(testOrder); AcBinaryDeserializer.Populate(acBinaryNoRef, target); } var acPopulateMs = sw.Elapsed.TotalMilliseconds; // === PopulateMerge (AcBinary only) === sw.Restart(); for (int i = 0; i < DefaultIterations; i++) { var target = CreatePopulateTarget(testOrder); AcBinaryDeserializer.PopulateMerge(acBinaryNoRef.AsSpan(), target); } var acMergeMs = sw.Elapsed.TotalMilliseconds; // Print performance table PrintTableHeader("PERFORMANCE COMPARISON (lower is better)"); PrintTableRow("Serialize (WithRef)", acWithRefSerMs, msgPackSerMs); PrintTableRow("Serialize (NoRef)", acNoRefSerMs, msgPackSerMs); PrintTableRow("Deserialize (WithRef)", acWithRefDeserMs, msgPackDeserMs); PrintTableRow("Deserialize (NoRef)", acNoRefDeserMs, msgPackDeserMs); PrintTableRow("Populate (NoRef)", acPopulateMs, 0); PrintTableRow("Merge (NoRef)", acMergeMs, 0); PrintTableRow("Round-trip (WithRef)", acWithRefSerMs + acWithRefDeserMs, msgPackSerMs + msgPackDeserMs); PrintTableRow("Round-trip (NoRef)", acNoRefSerMs + acNoRefDeserMs, msgPackSerMs + msgPackDeserMs); PrintTableFooter(); PrintSummary(acBinaryNoRef.Length, msgPackData.Length, acNoRefSerMs, msgPackSerMs, acNoRefDeserMs, msgPackDeserMs); // Assertions Assert.IsTrue(acBinaryWithRef.Length < msgPackData.Length, "AcBinary WithRef should be smaller than MessagePack"); Assert.IsTrue(acBinaryNoRef.Length < msgPackData.Length, "AcBinary NoRef should be smaller than MessagePack"); } [TestMethod] public void RunWithRefVsNoRefComparison() { PrintBanner("AcBinary WithRef vs NoRef Comparison"); // Create test data WITH shared references (to show WithRef advantage) TestDataFactory.ResetIdCounter(); var sharedTag = TestDataFactory.CreateTag("SharedTag"); var sharedUser = TestDataFactory.CreateUser("shareduser"); var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true); var testOrder = TestDataFactory.CreateOrder( itemCount: 5, palletsPerItem: 4, measurementsPerPallet: 3, pointsPerMeasurement: 5, sharedTag: sharedTag, sharedUser: sharedUser, sharedMetadata: sharedMeta); var withRefOptions = new AcBinarySerializerOptions(); var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling(); // Warmup Console.WriteLine("Warming up..."); for (int i = 0; i < 50; i++) { _ = AcBinarySerializer.Serialize(testOrder, withRefOptions); _ = AcBinarySerializer.Serialize(testOrder, noRefOptions); } var withRefData = AcBinarySerializer.Serialize(testOrder, withRefOptions); var noRefData = AcBinarySerializer.Serialize(testOrder, noRefOptions); Console.WriteLine($"Iterations: {DefaultIterations:N0}"); // Size comparison PrintBanner("SIZE COMPARISON (bytes)"); Console.WriteLine($"WithRef : {withRefData.Length,12:N0} (baseline)"); var sizeDiff = noRefData.Length - withRefData.Length; var ratio = withRefData.Length == 0 ? 0 : 100.0 * noRefData.Length / withRefData.Length; Console.WriteLine($"NoRef : {noRefData.Length,12:N0} (diff {sizeDiff:+#;-#;0}) => {ratio:F1}% of WithRef"); var sw = Stopwatch.StartNew(); // Serialize WithRef sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinarySerializer.Serialize(testOrder, withRefOptions); var withRefSerMs = sw.Elapsed.TotalMilliseconds; // Serialize NoRef sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinarySerializer.Serialize(testOrder, noRefOptions); var noRefSerMs = sw.Elapsed.TotalMilliseconds; // Deserialize WithRef sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinaryDeserializer.Deserialize(withRefData); var withRefDeserMs = sw.Elapsed.TotalMilliseconds; // Deserialize NoRef sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinaryDeserializer.Deserialize(noRefData); var noRefDeserMs = sw.Elapsed.TotalMilliseconds; PrintBanner("PERFORMANCE COMPARISON (ms)"); Console.WriteLine($"Serialize -> WithRef: {withRefSerMs,8:F2} | NoRef: {noRefSerMs,8:F2}"); Console.WriteLine($"Deserialize-> WithRef: {withRefDeserMs,8:F2} | NoRef: {noRefDeserMs,8:F2}"); Console.WriteLine($"Round-trip -> WithRef: {withRefSerMs + withRefDeserMs,8:F2} | NoRef: {noRefSerMs + noRefDeserMs,8:F2}"); if (withRefData.Length < noRefData.Length) { Console.WriteLine($"[OK] WithRef saves {noRefData.Length - withRefData.Length:N0} bytes by deduplicating shared references."); } else { Console.WriteLine($"[INFO] NoRef saves {withRefData.Length - noRefData.Length:N0} bytes because it skips reference metadata."); } // Verify correctness var resultWithRef = AcBinaryDeserializer.Deserialize(withRefData); var resultNoRef = AcBinaryDeserializer.Deserialize(noRefData); Assert.IsNotNull(resultWithRef); Assert.IsNotNull(resultNoRef); Assert.AreEqual(testOrder.Id, resultWithRef.Id); Assert.AreEqual(testOrder.Id, resultNoRef.Id); } [TestMethod] public void RunPopulateAndMergeBenchmark() { PrintBanner("AcBinary Populate & Merge Benchmark"); TestDataFactory.ResetIdCounter(); var testOrder = TestDataFactory.CreateBenchmarkOrder( itemCount: 3, palletsPerItem: 3, measurementsPerPallet: 3, pointsPerMeasurement: 4); var options = AcBinarySerializerOptions.WithoutReferenceHandling(); var binaryData = AcBinarySerializer.Serialize(testOrder, options); Console.WriteLine("Warming up..."); for (int i = 0; i < 50; i++) { var target = CreatePopulateTarget(testOrder); AcBinaryDeserializer.Populate(binaryData, target); AcBinaryDeserializer.PopulateMerge(binaryData.AsSpan(), target); } Console.WriteLine($"Iterations: {DefaultIterations:N0}"); Console.WriteLine($"Data size: {binaryData.Length:N0} bytes"); var sw = Stopwatch.StartNew(); // Deserialize (creates new object) sw.Restart(); for (int i = 0; i < DefaultIterations; i++) _ = AcBinaryDeserializer.Deserialize(binaryData); var deserializeMs = sw.Elapsed.TotalMilliseconds; // Populate (reuses existing object) sw.Restart(); for (int i = 0; i < DefaultIterations; i++) { var target = CreatePopulateTarget(testOrder); AcBinaryDeserializer.Populate(binaryData, target); } var populateMs = sw.Elapsed.TotalMilliseconds; // PopulateMerge (IId-based merge) sw.Restart(); for (int i = 0; i < DefaultIterations; i++) { var target = CreatePopulateTarget(testOrder); AcBinaryDeserializer.PopulateMerge(binaryData.AsSpan(), target); } var mergeMs = sw.Elapsed.TotalMilliseconds; // PopulateMerge with RemoveOrphanedItems var mergeWithRemoveOptions = new AcBinarySerializerOptions { RemoveOrphanedItems = true }; sw.Restart(); for (int i = 0; i < DefaultIterations; i++) { var target = CreatePopulateTarget(testOrder); AcBinaryDeserializer.PopulateMerge(binaryData.AsSpan(), target, mergeWithRemoveOptions); } var mergeWithRemoveMs = sw.Elapsed.TotalMilliseconds; PrintBanner("OPERATION COMPARISON (ms)"); Console.WriteLine($"Deserialize (new object): {deserializeMs:F2} (baseline)"); Console.WriteLine($"Populate (reuse obj) : {populateMs:F2} ({populateMs / deserializeMs:F2}x of baseline)"); Console.WriteLine($"PopulateMerge : {mergeMs:F2} ({mergeMs / deserializeMs:F2}x of baseline)"); Console.WriteLine($"PopulateMerge + cleanup: {mergeWithRemoveMs:F2} ({mergeWithRemoveMs / deserializeMs:F2}x of baseline)"); Console.WriteLine("[INFO] Populate/Merge reuse existing objects - ideal for UI data binding scenarios."); Assert.IsTrue(true); // Test passed if no exceptions } #endregion #region Test Models public class TestClassWithRepeatedValues { public int Id { get; set; } public string Status { get; set; } = ""; public string Category { get; set; } = ""; public string Priority { get; set; } = ""; } #endregion }