using AyCode.Core.Compression; using AyCode.Core.Serializers.Attributes; using AyCode.Core.Serializers.Binaries; using AyCode.Core.Tests.Serialization; // DrainFromAsync extension (test-only, used by benchmark) using AyCode.Core.Tests.TestModels; using MemoryPack; #if !AYCODE_NATIVEAOT using MessagePack; using MessagePack.Resolvers; #endif using Microsoft.Extensions.Options; using System.Buffers; using System.Diagnostics; using System.IO.Pipelines; using System.IO.Pipes; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using AyCode.Core.Benchmarks.Workloads.Scenarios; namespace AyCode.Core.Serializers.Console; /// /// Comprehensive benchmark application for all serializers. /// Compares: AcBinary (all options), MemoryPack, 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 { // Configuration (constants, mutable state, attribute-flag aggregation) → Configuration.cs // BuildAcBinary + GetMemPack helpers → Benchmarks/BenchmarkOptions.cs public static void Main(string[] args) { // Set console encoding to UTF-8 for proper Unicode character display System.Console.OutputEncoding = Encoding.UTF8; // Setup validation — abort BEFORE any benchmark logic if MemoryPack baseline is invalid. // Done early so user is told immediately, not after warmup. BenchmarkLoop.ValidateMemoryPackSetup(); // CLI mode (args provided): run once, parse args, exit. Backward-compatible behaviour. if (args.Length > 0) { if (!TryParseCliArgs(args, out var layer, out var opMode, out var serializerMode)) return; // invalid args BenchmarkLoop.RunBenchmark(layer, opMode, serializerMode); return; } // Interactive mode (no args): loop the menu so the user doesn't have to restart between runs. // Q exits the menu (and the application). while (true) { var selection = Menu.ShowInteractiveMenu(); if (selection == null) return; // user pressed Q BenchmarkLoop.RunBenchmark(selection.Value.layer, BenchmarkOpMode.All, selection.Value.serializerMode); System.Console.WriteLine(); System.Console.WriteLine("─────────────────────────────────────────────────────────────────────"); System.Console.WriteLine("Returning to menu — press any key to continue, or Q to quit..."); var key = System.Console.ReadKey(intercept: true); if (key.Key == ConsoleKey.Q) return; System.Console.WriteLine(); } } /// /// Parses CLI arguments into (layer, opMode, serializerMode) and, as a side effect, the active /// charset (). Each arg is classified /// independently and case-insensitively, so multiple args combine in any order — e.g. /// FastestByte AsciiShort or Serialize Large Latin1Short. Per arg, in order: /// "quick" (mutates warmup/iter counts), , /// , , then a charset name /// (see ). Unrecognized args are warned and ignored; dimensions left /// unset keep their defaults (All, All, Standard, and the /// field default for charset). Always returns true (kept for caller-side abort symmetry). /// private static bool TryParseCliArgs(string[] args, out BenchmarkLayer layer, out BenchmarkOpMode opMode, out SerializerSelectionMode serializerMode) { layer = BenchmarkLayer.All; opMode = BenchmarkOpMode.All; serializerMode = SerializerSelectionMode.Standard; // Each arg is classified independently → multiple args combine in any order. Without the // charset branch the CLI path never sets the charset, so it silently used the Latin1Long // field default — diverging from interactive runs (where the menu pins it). foreach (var arg in args) { // Quick mode: short warmup, few iterations, small sample count. Not an enum value — it's a // Configuration meta-flag, so handle it before the enum-parse cascade. if (string.Equals(arg, "quick", StringComparison.OrdinalIgnoreCase)) { Configuration.WarmupIterations = 5; Configuration.TestIterations = 100; Configuration.BenchmarkSamples = 3; continue; } // Serializer-selection (AsyncPipe/FastestByte/Standard). if (Enum.TryParse(arg, ignoreCase: true, out var sm)) { serializerMode = sm; continue; } // Op-mode (Serialize/Deserialize/All). if (Enum.TryParse(arg, ignoreCase: true, out var om)) { opMode = om; continue; } // Layer (Core/Comprehensive/Edge/Small/Medium/Large/Repeated/Deep/All). if (Enum.TryParse(arg, ignoreCase: true, out var ly)) { layer = ly; continue; } // Charset (long-string suffix profile) — mirrors the interactive ShowCharsetSettingsMenu. if (TryApplyCharsetArg(arg)) continue; // Unknown arg — ignored, defaults stand. Matches prior unrecognized-arg leniency. System.Console.Error.WriteLine($"Warning: unrecognized argument '{arg}'. Ignored (defaults: Layer=All, OpMode=All, SerializerMode=Standard, charset unchanged)."); } return true; } /// /// Maps a case-insensitive charset name to its value and assigns /// . Names mirror the interactive /// ShowCharsetSettingsMenu options. members are const string, /// so this is a name→value match rather than an . /// Returns false when the name is not a known charset (the caller then treats the arg as unknown). /// private static bool TryApplyCharsetArg(string arg) { string? suffix = arg.ToLowerInvariant() switch { "asciifix" => CharsetSuffixes.AsciiFix, "asciishort" => CharsetSuffixes.AsciiShort, "asciilong" => CharsetSuffixes.AsciiLong, "latin1fix" => CharsetSuffixes.Latin1Fix, "latin1short" => CharsetSuffixes.Latin1Short, "latin1long" => CharsetSuffixes.Latin1Long, "cjkbmpshort" => CharsetSuffixes.CjkBmpShort, "cjkbmplong" => CharsetSuffixes.CjkBmpLong, "cyrillicshort" => CharsetSuffixes.CyrillicShort, "cyrilliclong" => CharsetSuffixes.CyrillicLong, "mixedshort" => CharsetSuffixes.MixedShort, "mixedlong" => CharsetSuffixes.MixedLong, _ => null }; if (suffix is null) return false; BenchmarkTestDataProvider.LongStringSuffix = suffix; return true; } // RunBenchmark + RunBenchmarksForTestData + CreateSerializers → BenchmarkLoop.cs // Serializer implementations (ISerializerBenchmark + 12 concrete benchmark classes) → Benchmarks/ // Results / output formatters → Output.cs // BenchmarkResult DTO → BenchmarkResult.cs }