179 lines
8.0 KiB
C#
179 lines
8.0 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses CLI arguments into (layer, opMode, serializerMode) and, as a side effect, the active
|
|
/// charset (<see cref="BenchmarkTestDataProvider.LongStringSuffix"/>). Each arg is classified
|
|
/// independently and case-insensitively, so multiple args combine in any order — e.g.
|
|
/// <c>FastestByte AsciiShort</c> or <c>Serialize Large Latin1Short</c>. Per arg, in order:
|
|
/// <c>"quick"</c> (mutates <see cref="Configuration"/> warmup/iter counts), <see cref="SerializerSelectionMode"/>,
|
|
/// <see cref="BenchmarkOpMode"/>, <see cref="BenchmarkLayer"/>, then a charset name
|
|
/// (see <see cref="TryApplyCharsetArg"/>). Unrecognized args are warned and ignored; dimensions left
|
|
/// unset keep their defaults (All, All, Standard, and the <see cref="BenchmarkTestDataProvider.LongStringSuffix"/>
|
|
/// field default for charset). Always returns <c>true</c> (kept for caller-side abort symmetry).
|
|
/// </summary>
|
|
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<SerializerSelectionMode>(arg, ignoreCase: true, out var sm))
|
|
{
|
|
serializerMode = sm;
|
|
continue;
|
|
}
|
|
|
|
// Op-mode (Serialize/Deserialize/All).
|
|
if (Enum.TryParse<BenchmarkOpMode>(arg, ignoreCase: true, out var om))
|
|
{
|
|
opMode = om;
|
|
continue;
|
|
}
|
|
|
|
// Layer (Core/Comprehensive/Edge/Small/Medium/Large/Repeated/Deep/All).
|
|
if (Enum.TryParse<BenchmarkLayer>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maps a case-insensitive charset name to its <see cref="CharsetSuffixes"/> value and assigns
|
|
/// <see cref="BenchmarkTestDataProvider.LongStringSuffix"/>. Names mirror the interactive
|
|
/// <c>ShowCharsetSettingsMenu</c> options. <see cref="CharsetSuffixes"/> members are <c>const string</c>,
|
|
/// so this is a name→value match rather than an <see cref="Enum.TryParse{T}(string, bool, out T)"/>.
|
|
/// Returns <c>false</c> when the name is not a known charset (the caller then treats the arg as unknown).
|
|
/// </summary>
|
|
private static bool TryApplyCharsetArg(string arg)
|
|
{
|
|
string? suffix = arg.ToLowerInvariant() switch
|
|
{
|
|
"latin1fixascii" => CharsetSuffixes.Latin1FixAscii,
|
|
"asciishort" => CharsetSuffixes.AsciiShort,
|
|
"asciilong" => CharsetSuffixes.AsciiLong,
|
|
"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
|
|
|
|
}
|