using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Jsons; using AyCode.Core.Tests.TestModels; using MessagePack; using MessagePack.Resolvers; using Newtonsoft.Json; namespace AyCode.Core.Serializers.Console; /// /// Comprehensive benchmark application for all serializers. /// Compares: AcBinary (all options), AcJson, MessagePack, Newtonsoft.Json, System.Text.Json /// /// Usage: /// dotnet run # Run all benchmarks /// dotnet run -- quick # Quick mode (fewer iterations) /// dotnet run -- serialize # Serialize only /// dotnet run -- deserialize # Deserialize only /// public static class Program { private const string ResultsDirectory = @"H:\Applications\Aycode\Source\AyCode.Core\Test_Benchmark_Results\Benchmark"; #if DEBUG private const string BuildConfiguration = "Debug"; #else private const string BuildConfiguration = "Release"; #endif // Serializer name constants private const string SerializerMessagePack = "MessagePack"; private const string SerializerAcBinaryDefault = "AcBinary (Default)"; private const string SerializerAcBinaryNoRef = "AcBinary (NoRef)"; private const string SerializerAcBinaryFastMode = "AcBinary (FastMode)"; private const string SerializerAcBinaryNoIntern = "AcBinary (NoIntern)"; private const string SerializerAcJsonDefault = "AcJson (Default)"; private const string SerializerNewtonsoftJson = "Newtonsoft.Json"; private const string SerializerSystemTextJson = "System.Text.Json"; private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false); private static int WarmupIterations = 10; private static int TestIterations = 1000; public static void Main(string[] args) { // Set console encoding to UTF-8 for proper Unicode character display System.Console.OutputEncoding = System.Text.Encoding.UTF8; var mode = args.Length > 0 ? args[0].ToLower() : "all"; if (mode == "quick") { WarmupIterations = 10; TestIterations = 100; mode = "all"; } System.Console.WriteLine("╔══════════════════════════════════════════════════════════════════════╗"); System.Console.WriteLine("║ COMPREHENSIVE SERIALIZER BENCHMARK SUITE ║"); System.Console.WriteLine("╚══════════════════════════════════════════════════════════════════════╝"); System.Console.WriteLine($"Mode: {mode} | Iterations: {TestIterations} | Warmup: {WarmupIterations}"); System.Console.WriteLine($"Build: {BuildConfiguration} | .NET: {Environment.Version}"); System.Console.WriteLine(); var allResults = new List(); var testDataSets = CreateTestDataSets(); foreach (var testData in testDataSets) { System.Console.WriteLine($"\n{'═'.ToString().PadRight(70, '═')}"); System.Console.WriteLine($"TEST DATA: {testData.Name}"); System.Console.WriteLine($"{'═'.ToString().PadRight(70, '═')}"); var results = RunBenchmarksForTestData(testData, mode); allResults.AddRange(results); } // Print grouped results PrintGroupedResults(allResults, testDataSets); // Save results to file SaveResults(allResults, testDataSets); System.Console.WriteLine("\n✓ Benchmark complete!"); } #region Test Data Creation private static List CreateTestDataSets() { return new List { CreateSmallTestData(), CreateMediumTestData(), CreateLargeTestData(), CreateRepeatedStringsTestData(), CreateDeepNestedTestData() }; } private static TestDataSet CreateSmallTestData() { TestDataFactory.ResetIdCounter(); var order = TestDataFactory.CreateOrder( itemCount: 2, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 2); return new TestDataSet("Small (2x2x2x2)", order); } private static TestDataSet CreateMediumTestData() { TestDataFactory.ResetIdCounter(); var sharedTag = TestDataFactory.CreateTag("SharedTag"); var sharedUser = TestDataFactory.CreateUser("shareduser"); var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true); var order = TestDataFactory.CreateOrder( itemCount: 3, palletsPerItem: 3, measurementsPerPallet: 3, pointsPerMeasurement: 4, sharedTag: sharedTag, sharedUser: sharedUser, sharedMetadata: sharedMeta); return new TestDataSet("Medium (3x3x3x4, shared refs)", order); } private static TestDataSet CreateLargeTestData() { TestDataFactory.ResetIdCounter(); var sharedTag = TestDataFactory.CreateTag("SharedTag"); var sharedUser = TestDataFactory.CreateUser("shareduser"); var order = TestDataFactory.CreateOrder( itemCount: 5, palletsPerItem: 5, measurementsPerPallet: 5, pointsPerMeasurement: 10, sharedTag: sharedTag, sharedUser: sharedUser); return new TestDataSet("Large (5x5x5x10)", order); } private static TestDataSet CreateRepeatedStringsTestData() { TestDataFactory.ResetIdCounter(); // Create order with many items to test string interning on repeated property names var order = TestDataFactory.CreateOrder( itemCount: 10, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 2); // Set same status and ProductName on all items to test enum and string handling foreach (var item in order.Items) { item.Status = TestStatus.Processing; item.ProductName = "CommonProductName_RepeatedForTesting"; } return new TestDataSet("Repeated Strings (10 items)", order); } private static TestDataSet CreateDeepNestedTestData() { TestDataFactory.ResetIdCounter(); var order = TestDataFactory.CreateOrder( itemCount: 2, palletsPerItem: 4, measurementsPerPallet: 4, pointsPerMeasurement: 8); return new TestDataSet("Deep Nested (2x4x4x8)", order); } #endregion #region Benchmark Execution private static List RunBenchmarksForTestData(TestDataSet testData, string mode) { var results = new List(); var serializers = CreateSerializers(testData); // Warmup all serializers System.Console.WriteLine($"Warming up ({WarmupIterations} iterations)..."); foreach (var serializer in serializers) { serializer.Warmup(WarmupIterations); } // Run benchmarks System.Console.WriteLine($"Running benchmarks ({TestIterations} iterations)...\n"); foreach (var serializer in serializers) { var result = new BenchmarkResult { TestDataName = testData.Name, SerializerName = serializer.Name, SerializedSize = serializer.SerializedSize }; if (mode is "all" or "serialize" or "ser") { result.SerializeTimeMs = RunTimed(() => serializer.Serialize(), TestIterations); } if (mode is "all" or "deserialize" or "des") { result.DeserializeTimeMs = RunTimed(() => serializer.Deserialize(), TestIterations); } results.Add(result); PrintResult(result); } return results; } private static List CreateSerializers(TestDataSet testData) { return new List { // AcBinary variants new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling(), SerializerAcBinaryNoRef), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = false }, SerializerAcBinaryNoIntern), // AcJson new AcJsonBenchmark(testData.Order, AcJsonSerializerOptions.Default, SerializerAcJsonDefault), // MessagePack new MessagePackBenchmark(testData.Order, SerializerMessagePack), // Newtonsoft.Json new NewtonsoftBenchmark(testData.Order, SerializerNewtonsoftJson), // System.Text.Json new SystemTextJsonBenchmark(testData.Order, SerializerSystemTextJson) }; } private static double RunTimed(Action action, int iterations) { var sw = Stopwatch.StartNew(); for (var i = 0; i < iterations; i++) { action(); } sw.Stop(); return sw.Elapsed.TotalMilliseconds; } #endregion #region Serializer Implementations private interface ISerializerBenchmark { string Name { get; } int SerializedSize { get; } void Warmup(int iterations); void Serialize(); void Deserialize(); } private sealed class AcBinaryBenchmark : ISerializerBenchmark { private readonly TestOrder _order; private readonly AcBinarySerializerOptions _options; private readonly byte[] _serialized; public string Name { get; } public int SerializedSize => _serialized.Length; public AcBinaryBenchmark(TestOrder order, AcBinarySerializerOptions options, string name) { _order = order; _options = options; Name = name; _serialized = AcBinarySerializer.Serialize(order, options); } public void Warmup(int iterations) { for (var i = 0; i < iterations; i++) { Serialize(); Deserialize(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void Serialize() => AcBinarySerializer.Serialize(_order, _options); [MethodImpl(MethodImplOptions.NoInlining)] public void Deserialize() => AcBinaryDeserializer.Deserialize(_serialized, _options); } private sealed class AcJsonBenchmark : ISerializerBenchmark { private readonly TestOrder _order; private readonly AcJsonSerializerOptions _options; private readonly string _serialized; private readonly byte[] _serializedUtf8; public string Name { get; } public int SerializedSize => _serializedUtf8.Length; public AcJsonBenchmark(TestOrder order, AcJsonSerializerOptions options, string name) { _order = order; _options = options; Name = name; _serialized = AcJsonSerializer.Serialize(order, options); _serializedUtf8 = Utf8NoBom.GetBytes(_serialized); } public void Warmup(int iterations) { for (var i = 0; i < iterations; i++) { Serialize(); Deserialize(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void Serialize() => AcJsonSerializer.Serialize(_order, _options); [MethodImpl(MethodImplOptions.NoInlining)] public void Deserialize() => AcJsonDeserializer.Deserialize(_serialized); } private sealed class MessagePackBenchmark : ISerializerBenchmark { private readonly TestOrder _order; private readonly MessagePackSerializerOptions _options; private readonly byte[] _serialized; public string Name { get; } public int SerializedSize => _serialized.Length; public MessagePackBenchmark(TestOrder order, string name) { _order = order; Name = name; _options = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None); _serialized = MessagePackSerializer.Serialize(order, _options); } public void Warmup(int iterations) { for (var i = 0; i < iterations; i++) { Serialize(); Deserialize(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void Serialize() => MessagePackSerializer.Serialize(_order, _options); [MethodImpl(MethodImplOptions.NoInlining)] public void Deserialize() => MessagePackSerializer.Deserialize(_serialized, _options); } private sealed class NewtonsoftBenchmark : ISerializerBenchmark { private readonly TestOrder _order; private readonly JsonSerializerSettings _settings; private readonly string _serialized; private readonly byte[] _serializedUtf8; public string Name { get; } public int SerializedSize => _serializedUtf8.Length; public NewtonsoftBenchmark(TestOrder order, string name) { _order = order; Name = name; _settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, NullValueHandling = NullValueHandling.Ignore }; _serialized = JsonConvert.SerializeObject(order, _settings); _serializedUtf8 = Utf8NoBom.GetBytes(_serialized); } public void Warmup(int iterations) { for (var i = 0; i < iterations; i++) { Serialize(); Deserialize(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void Serialize() => JsonConvert.SerializeObject(_order, _settings); [MethodImpl(MethodImplOptions.NoInlining)] public void Deserialize() => JsonConvert.DeserializeObject(_serialized, _settings); } private sealed class SystemTextJsonBenchmark : ISerializerBenchmark { private readonly TestOrder _order; private readonly JsonSerializerOptions _options; private readonly string _serialized; private readonly byte[] _serializedUtf8; public string Name { get; } public int SerializedSize => _serializedUtf8.Length; public SystemTextJsonBenchmark(TestOrder order, string name) { _order = order; Name = name; _options = new JsonSerializerOptions { WriteIndented = false, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles }; _serialized = System.Text.Json.JsonSerializer.Serialize(order, _options); _serializedUtf8 = Utf8NoBom.GetBytes(_serialized); } public void Warmup(int iterations) { for (var i = 0; i < iterations; i++) { Serialize(); Deserialize(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void Serialize() => System.Text.Json.JsonSerializer.Serialize(_order, _options); [MethodImpl(MethodImplOptions.NoInlining)] public void Deserialize() => System.Text.Json.JsonSerializer.Deserialize(_serialized, _options); } #endregion #region Results private sealed class TestDataSet { public string Name { get; } public TestOrder Order { get; } public TestDataSet(string name, TestOrder order) { Name = name; Order = order; } } private sealed class BenchmarkResult { public string TestDataName { get; set; } = ""; public string SerializerName { get; set; } = ""; public int SerializedSize { get; set; } public double SerializeTimeMs { get; set; } public double DeserializeTimeMs { get; set; } public double RoundTripTimeMs => SerializeTimeMs + DeserializeTimeMs; } private static void PrintResult(BenchmarkResult result) { var ser = result.SerializeTimeMs > 0 ? $"{result.SerializeTimeMs,8:F2} ms" : " N/A"; var des = result.DeserializeTimeMs > 0 ? $"{result.DeserializeTimeMs,8:F2} ms" : " N/A"; System.Console.WriteLine($" {result.SerializerName,-25} | Size: {result.SerializedSize,8:N0} | Ser: {ser} | Des: {des}"); } private static void PrintGroupedResults(List results, List testDataSets) { System.Console.WriteLine("\n"); System.Console.WriteLine("╔══════════════════════════════════════════════════════════════════════════════════════════════════════╗"); System.Console.WriteLine("║ GROUPED RESULTS BY TEST DATA ║"); System.Console.WriteLine("╚══════════════════════════════════════════════════════════════════════════════════════════════════════╝"); foreach (var testData in testDataSets) { var testResults = results.Where(r => r.TestDataName == testData.Name).OrderBy(r => r.RoundTripTimeMs).ToList(); var msgPackResult = testResults.FirstOrDefault(r => r.SerializerName == SerializerMessagePack); var acBinaryResult = testResults.FirstOrDefault(r => r.SerializerName == SerializerAcBinaryDefault); System.Console.WriteLine($"\n┌─ {testData.Name} ─".PadRight(98, '─') + "┐"); System.Console.WriteLine($"│ {"#",-4} │ {"Serializer",-25} │ {"Size",-10} │ {"Serialize",-12} │ {"Deserialize",-12} │ {"Round-trip",-12} │"); System.Console.WriteLine($"├{"─".PadRight(6, '─')}┼{"─".PadRight(27, '─')}┼{"─".PadRight(12, '─')}┼{"─".PadRight(14, '─')}┼{"─".PadRight(14, '─')}┼{"─".PadRight(14, '─')}┤"); var rank = 1; foreach (var result in testResults) { var size = $"{result.SerializedSize:N0}"; var ser = result.SerializeTimeMs > 0 ? $"{result.SerializeTimeMs:F2} ms" : "N/A"; var des = result.DeserializeTimeMs > 0 ? $"{result.DeserializeTimeMs:F2} ms" : "N/A"; var rt = result.RoundTripTimeMs > 0 ? $"{result.RoundTripTimeMs:F2} ms" : "N/A"; // Highlight MessagePack and AcBinary (Default) with win/lose colors var isHighlighted = result.SerializerName is SerializerMessagePack or SerializerAcBinaryDefault; var prefix = isHighlighted ? "│►" : "│ "; var suffix = isHighlighted ? "◄│" : " │"; // Color logic: Green = winner (faster), Red = loser (slower) if (isHighlighted && msgPackResult != null && acBinaryResult != null) { var isMsgPack = result.SerializerName == SerializerMessagePack; var msgPackFaster = msgPackResult.RoundTripTimeMs < acBinaryResult.RoundTripTimeMs; if (isMsgPack) { System.Console.ForegroundColor = msgPackFaster ? ConsoleColor.Green : ConsoleColor.Red; } else { System.Console.ForegroundColor = msgPackFaster ? ConsoleColor.Red : ConsoleColor.Green; } } System.Console.WriteLine($"{prefix}{rank++,4} │ {result.SerializerName,-25} │ {size,10} │ {ser,12} │ {des,12} │ {rt,12}{suffix}"); if (isHighlighted) { System.Console.ResetColor(); } } // Footer row: AcBinary (Default) vs MessagePack comparison per column if (msgPackResult != null && acBinaryResult != null) { var sizePct = (acBinaryResult.SerializedSize / (double)msgPackResult.SerializedSize - 1) * 100; var serPct = msgPackResult.SerializeTimeMs > 0 ? (acBinaryResult.SerializeTimeMs / msgPackResult.SerializeTimeMs - 1) * 100 : 0; var desPct = msgPackResult.DeserializeTimeMs > 0 ? (acBinaryResult.DeserializeTimeMs / msgPackResult.DeserializeTimeMs - 1) * 100 : 0; var rtPct = msgPackResult.RoundTripTimeMs > 0 ? (acBinaryResult.RoundTripTimeMs / msgPackResult.RoundTripTimeMs - 1) * 100 : 0; System.Console.WriteLine($"├{"─".PadRight(6, '─')}┴{"─".PadRight(27, '─')}┼{"─".PadRight(12, '─')}┼{"─".PadRight(14, '─')}┼{"─".PadRight(14, '─')}┼{"─".PadRight(13, '─')}┤"); System.Console.Write($"│ ► Default vs {SerializerMessagePack,-19} │ "); // Size System.Console.ForegroundColor = sizePct <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.Write($"{sizePct,+9:+0;-0}%"); System.Console.ResetColor(); System.Console.Write(" │ "); // Serialize System.Console.ForegroundColor = serPct <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.Write($"{serPct,+11:+0;-0}%"); System.Console.ResetColor(); System.Console.Write(" │ "); // Deserialize System.Console.ForegroundColor = desPct <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.Write($"{desPct,+11:+0;-0}%"); System.Console.ResetColor(); System.Console.Write(" │ "); // Round-trip System.Console.ForegroundColor = rtPct <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.Write($"{rtPct,+10:+0;-0}%"); System.Console.ResetColor(); System.Console.WriteLine(" │"); } System.Console.WriteLine($"└{"─".PadRight(6, '─')}─{"─".PadRight(27, '─')}┴{"─".PadRight(12, '─')}┴{"─".PadRight(14, '─')}┴{"─".PadRight(14, '─')}┴{"─".PadRight(13, '─')}┘"); } // Summary: Best serializer for each category System.Console.WriteLine("\n"); System.Console.WriteLine("╔══════════════════════════════════════════════════════════════════════════════════════════════════════╗"); System.Console.WriteLine("║ SUMMARY: WINNERS ║"); System.Console.WriteLine("╚══════════════════════════════════════════════════════════════════════════════════════════════════════╝"); System.Console.WriteLine($"\n{"Category",-20} │ {"Winner",-25} │ {"Avg Value",-18}"); System.Console.WriteLine($"{"─".PadRight(20, '─')}─┼─{"─".PadRight(25, '─')}─┼─{"─".PadRight(18, '─')}"); // Fastest Serialize var fastestSer = results.Where(r => r.SerializeTimeMs > 0) .GroupBy(r => r.SerializerName) .Select(g => new { Name = g.Key, AvgTime = g.Average(r => r.SerializeTimeMs) }) .OrderBy(x => x.AvgTime) .FirstOrDefault(); if (fastestSer != null) System.Console.WriteLine($"{"Fastest Serialize",-20} │ {fastestSer.Name,-25} │ {fastestSer.AvgTime,15:F2} ms"); // Fastest Deserialize var fastestDes = results.Where(r => r.DeserializeTimeMs > 0) .GroupBy(r => r.SerializerName) .Select(g => new { Name = g.Key, AvgTime = g.Average(r => r.DeserializeTimeMs) }) .OrderBy(x => x.AvgTime) .FirstOrDefault(); if (fastestDes != null) System.Console.WriteLine($"{"Fastest Deserialize",-20} │ {fastestDes.Name,-25} │ {fastestDes.AvgTime,15:F2} ms"); // Smallest Size var smallestSize = results .GroupBy(r => r.SerializerName) .Select(g => new { Name = g.Key, AvgSize = g.Average(r => r.SerializedSize) }) .OrderBy(x => x.AvgSize) .FirstOrDefault(); if (smallestSize != null) System.Console.WriteLine($"{"Smallest Size",-20} │ {smallestSize.Name,-25} │ {smallestSize.AvgSize,15:F0} B"); // Fastest Round-trip var fastestRt = results.Where(r => r.RoundTripTimeMs > 0) .GroupBy(r => r.SerializerName) .Select(g => new { Name = g.Key, AvgTime = g.Average(r => r.RoundTripTimeMs) }) .OrderBy(x => x.AvgTime) .FirstOrDefault(); if (fastestRt != null) System.Console.WriteLine($"{"Fastest Round-trip",-20} │ {fastestRt.Name,-25} │ {fastestRt.AvgTime,15:F2} ms"); // Overall AcBinary Default vs MessagePack comparison var msgPackAvgSer = results.Where(r => r.SerializerName == SerializerMessagePack && r.SerializeTimeMs > 0).Average(r => r.SerializeTimeMs); var msgPackAvgDes = results.Where(r => r.SerializerName == SerializerMessagePack && r.DeserializeTimeMs > 0).Average(r => r.DeserializeTimeMs); var msgPackAvgRt = results.Where(r => r.SerializerName == SerializerMessagePack && r.RoundTripTimeMs > 0).Average(r => r.RoundTripTimeMs); var msgPackAvgSize = results.Where(r => r.SerializerName == SerializerMessagePack).Average(r => r.SerializedSize); var acBinaryAvgSer = results.Where(r => r.SerializerName == SerializerAcBinaryDefault && r.SerializeTimeMs > 0).Average(r => r.SerializeTimeMs); var acBinaryAvgDes = results.Where(r => r.SerializerName == SerializerAcBinaryDefault && r.DeserializeTimeMs > 0).Average(r => r.DeserializeTimeMs); var acBinaryAvgRt = results.Where(r => r.SerializerName == SerializerAcBinaryDefault && r.RoundTripTimeMs > 0).Average(r => r.RoundTripTimeMs); var acBinaryAvgSize = results.Where(r => r.SerializerName == SerializerAcBinaryDefault).Average(r => r.SerializedSize); System.Console.WriteLine(); System.Console.WriteLine($"── {SerializerAcBinaryDefault} vs {SerializerMessagePack} (Overall) ──"); var serPctAll = (acBinaryAvgSer / msgPackAvgSer - 1) * 100; var desPctAll = (acBinaryAvgDes / msgPackAvgDes - 1) * 100; var rtPctAll = (acBinaryAvgRt / msgPackAvgRt - 1) * 100; var sizePctAll = (acBinaryAvgSize / msgPackAvgSize - 1) * 100; System.Console.ForegroundColor = serPctAll <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.WriteLine($" Serialize: {serPctAll:+0;-0}% ({acBinaryAvgSer:F2} ms vs {msgPackAvgSer:F2} ms)"); System.Console.ResetColor(); System.Console.ForegroundColor = desPctAll <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.WriteLine($" Deserialize: {desPctAll:+0;-0}% ({acBinaryAvgDes:F2} ms vs {msgPackAvgDes:F2} ms)"); System.Console.ResetColor(); System.Console.ForegroundColor = rtPctAll <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.WriteLine($" Round-trip: {rtPctAll:+0;-0}% ({acBinaryAvgRt:F2} ms vs {msgPackAvgRt:F2} ms)"); System.Console.ResetColor(); System.Console.ForegroundColor = sizePctAll <= 0 ? ConsoleColor.Green : ConsoleColor.Red; System.Console.WriteLine($" Size: {sizePctAll:+0;-0}% ({acBinaryAvgSize:F0} B vs {msgPackAvgSize:F0} B)"); System.Console.ResetColor(); } private static void SaveResults(List results, List testDataSets) { Directory.CreateDirectory(ResultsDirectory); var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); var fileName = $"Console.FullBenchmark_{BuildConfiguration}_{timestamp}.log"; var filePath = Path.Combine(ResultsDirectory, fileName); var sb = new StringBuilder(); sb.AppendLine("╔══════════════════════════════════════════════════════════════════════════════════════════════════════╗"); sb.AppendLine("║ SERIALIZER BENCHMARK RESULTS ║"); sb.AppendLine($"║ Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}".PadRight(100) + "║"); sb.AppendLine($"║ Build: {BuildConfiguration}".PadRight(100) + "║"); sb.AppendLine($"║ Iterations: {TestIterations}".PadRight(100) + "║"); sb.AppendLine("╚══════════════════════════════════════════════════════════════════════════════════════════════════════╝"); sb.AppendLine(); // CSV-like data for easy import sb.AppendLine("=== RAW DATA (CSV) ==="); sb.AppendLine("TestData,Serializer,Size,SerializeMs,DeserializeMs,RoundTripMs"); foreach (var testData in testDataSets) { var testResults = results.Where(r => r.TestDataName == testData.Name).ToList(); foreach (var result in testResults) { sb.AppendLine($"{result.TestDataName},{result.SerializerName},{result.SerializedSize},{result.SerializeTimeMs:F2},{result.DeserializeTimeMs:F2},{result.RoundTripTimeMs:F2}"); } } sb.AppendLine(); // Formatted results sb.AppendLine("=== FORMATTED RESULTS BY TEST DATA ==="); sb.AppendLine($"(►) = Highlighted: {SerializerMessagePack} (baseline) and {SerializerAcBinaryDefault}"); sb.AppendLine(); foreach (var testData in testDataSets) { var testResults = results.Where(r => r.TestDataName == testData.Name).OrderBy(r => r.RoundTripTimeMs).ToList(); var msgPackResult = testResults.FirstOrDefault(r => r.SerializerName == SerializerMessagePack); var acBinaryResult = testResults.FirstOrDefault(r => r.SerializerName == SerializerAcBinaryDefault); sb.AppendLine(); sb.AppendLine($"--- {testData.Name} ---"); sb.AppendLine($"{"#",-4} {"Serializer",-26} {"Size",-12} {"Serialize",-14} {"Deserialize",-14} {"Round-trip",-14}"); sb.AppendLine(new string('-', 86)); var rank = 1; foreach (var result in testResults) { var isHighlighted = result.SerializerName is SerializerMessagePack or SerializerAcBinaryDefault; var prefix = isHighlighted ? "► " : " "; var size = $"{result.SerializedSize:N0}"; var ser = result.SerializeTimeMs > 0 ? $"{result.SerializeTimeMs:F2} ms" : "N/A"; var des = result.DeserializeTimeMs > 0 ? $"{result.DeserializeTimeMs:F2} ms" : "N/A"; var rt = result.RoundTripTimeMs > 0 ? $"{result.RoundTripTimeMs:F2} ms" : "N/A"; sb.AppendLine($"{rank++,2} {prefix}{result.SerializerName,-24} {size,-12} {ser,-14} {des,-14} {rt,-14}"); } // Summary row for this test data if (msgPackResult != null && acBinaryResult != null) { var sizePct = (acBinaryResult.SerializedSize / (double)msgPackResult.SerializedSize - 1) * 100; var serPct = msgPackResult.SerializeTimeMs > 0 ? (acBinaryResult.SerializeTimeMs / msgPackResult.SerializeTimeMs - 1) * 100 : 0; var desPct = msgPackResult.DeserializeTimeMs > 0 ? (acBinaryResult.DeserializeTimeMs / msgPackResult.DeserializeTimeMs - 1) * 100 : 0; var rtPct = msgPackResult.RoundTripTimeMs > 0 ? (acBinaryResult.RoundTripTimeMs / msgPackResult.RoundTripTimeMs - 1) * 100 : 0; sb.AppendLine($" {SerializerAcBinaryDefault} vs {SerializerMessagePack}: Size {sizePct:+0;-0}% │ Ser {serPct:+0;-0}% │ Des {desPct:+0;-0}% │ RT {rtPct:+0;-0}%"); } } // Summary comparison sb.AppendLine(); sb.AppendLine($"=== {SerializerAcBinaryDefault} vs {SerializerMessagePack} (Overall) ==="); var msgPackAvgSer = results.Where(r => r.SerializerName == SerializerMessagePack && r.SerializeTimeMs > 0).Average(r => r.SerializeTimeMs); var msgPackAvgDes = results.Where(r => r.SerializerName == SerializerMessagePack && r.DeserializeTimeMs > 0).Average(r => r.DeserializeTimeMs); var msgPackAvgRt = results.Where(r => r.SerializerName == SerializerMessagePack && r.RoundTripTimeMs > 0).Average(r => r.RoundTripTimeMs); var msgPackAvgSize = results.Where(r => r.SerializerName == SerializerMessagePack).Average(r => r.SerializedSize); var acBinaryAvgSer = results.Where(r => r.SerializerName == SerializerAcBinaryDefault && r.SerializeTimeMs > 0).Average(r => r.SerializeTimeMs); var acBinaryAvgDes = results.Where(r => r.SerializerName == SerializerAcBinaryDefault && r.DeserializeTimeMs > 0).Average(r => r.DeserializeTimeMs); var acBinaryAvgRt = results.Where(r => r.SerializerName == SerializerAcBinaryDefault && r.RoundTripTimeMs > 0).Average(r => r.RoundTripTimeMs); var acBinaryAvgSize = results.Where(r => r.SerializerName == SerializerAcBinaryDefault).Average(r => r.SerializedSize); sb.AppendLine($" Serialize: {((acBinaryAvgSer / msgPackAvgSer - 1) * 100):+0;-0}% ({acBinaryAvgSer:F2} ms vs {msgPackAvgSer:F2} ms)"); sb.AppendLine($" Deserialize: {((acBinaryAvgDes / msgPackAvgDes - 1) * 100):+0;-0}% ({acBinaryAvgDes:F2} ms vs {msgPackAvgDes:F2} ms)"); sb.AppendLine($" Round-trip: {((acBinaryAvgRt / msgPackAvgRt - 1) * 100):+0;-0}% ({acBinaryAvgRt:F2} ms vs {msgPackAvgRt:F2} ms)"); sb.AppendLine($" Size: {((acBinaryAvgSize / msgPackAvgSize - 1) * 100):+0;-0}% ({acBinaryAvgSize:F0} B vs {msgPackAvgSize:F0} B)"); File.WriteAllText(filePath, sb.ToString(), Utf8NoBom); System.Console.WriteLine($"\n✓ Results saved to: {filePath}"); } #endregion }