121 lines
6.8 KiB
C#
121 lines
6.8 KiB
C#
using AyCode.Core.Serializers.Binaries;
|
||
using AyCode.Core.Tests.TestModels;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Text;
|
||
|
||
namespace AyCode.Core.Serializers.Console;
|
||
|
||
/// <summary>
|
||
/// Configuration state for the benchmark application. Holds compile-time and runtime constants,
|
||
/// per-run mutable settings (WireMode, charset, iteration counts), and the attribute-flag
|
||
/// aggregation that drives the per-row Options column. Split out from <c>Program.cs</c> so the
|
||
/// entry-point file can focus on UX-flow and benchmark orchestration; everything in this class
|
||
/// is config / state — no benchmark logic. Single instance (static class) — the application is
|
||
/// a one-shot process, no multi-tenant state isolation needed.
|
||
/// </summary>
|
||
internal static class Configuration
|
||
{
|
||
#if AYCODE_NATIVEAOT
|
||
internal const string BuildConfiguration = "NativeAOT";
|
||
#elif DEBUG
|
||
internal const string BuildConfiguration = "Debug";
|
||
#elif SGEN_ONLY
|
||
internal const string BuildConfiguration = "SGenOnly";
|
||
#else
|
||
internal const string BuildConfiguration = "Release";
|
||
#endif
|
||
|
||
#if DEBUG
|
||
internal static int WarmupIterations = 0;
|
||
internal static int TestIterations = 1;
|
||
internal static int BenchmarkSamples = 1; // Debug: single sample, fast iteration
|
||
#else
|
||
internal static int WarmupIterations = 5000; //10000 — per-phase (Ser AND Des get their own warmup separately)
|
||
internal static int TestIterations = 1000; //1000
|
||
internal static int BenchmarkSamples = 10;
|
||
#endif
|
||
|
||
// Interactive settings: selected AcBinary wire mode for benchmark runs.
|
||
// 1 = Compact, 2 = Fast
|
||
internal static WireMode SelectedWireMode = WireMode.Compact;
|
||
|
||
// Engine / IO mode / Dispatch mode identifiers → Benchmarks/BenchmarkEnums.cs (typed enums with ToDisplay)
|
||
|
||
// Single source of truth for the chunk size used by ALL pipe-related benchmarks (NamedPipe PipeChunk,
|
||
// NamedPipe PipeRaw, in-memory Pipe, in-memory RawMem) AND the NamedPipe server's inBufferSize/outBufferSize.
|
||
// Same value across both layers ensures apples-to-apples comparison: chunked-streaming chunk-on-wire size
|
||
// matches the kernel pipe-buffer slot exactly. Tweak HERE when experimenting; do NOT scatter chunkSize
|
||
// overrides across individual benchmark rows.
|
||
internal const int PipeChunkSize = 4096;
|
||
|
||
// Per-cell adaptive iteration target wall-clock duration. Each Ser/Des function calibrates its
|
||
// own iteration count post-warmup so the sample batch lands in this range — equalizes the
|
||
// per-sample window across cells of vastly different per-op cost (Small ~6 ns/op vs Large
|
||
// ~140 µs/op). Below ~100 ms Stopwatch precision and OS preempt spikes start to dominate.
|
||
internal const int TargetSampleMs = 250;
|
||
|
||
// CV (coefficient of variation = stddev / mean) threshold above which a row's range is flagged
|
||
// as "unstable" in the markdown output (⚠️ marker). 3% is a reasonable noise-floor expectation
|
||
// for stabilized in-memory benchmarks; rows above it should be discounted when reading
|
||
// sub-3% inter-engine deltas.
|
||
internal const double UnstableCVThreshold = 0.03;
|
||
|
||
// Lower-bound CV threshold for micro-optimization measurement reliability. Rows with CV in
|
||
// the (MicroOptCVThreshold, UnstableCVThreshold] range get a softer "⚠️micro" flag — they are
|
||
// not unstable enough to be entirely dismissable, but sub-2% inter-engine deltas observed on
|
||
// such a row are at the edge of the noise floor and should be cross-checked (re-run, BDN).
|
||
// Use case: micro-opt sprints where a ~1-2% signal lives below the unstable threshold but the
|
||
// row's own CV is still high enough to make that signal suspect.
|
||
internal const double MicroOptCVThreshold = 0.015;
|
||
|
||
// Inter-sample cool-down delay (ms) inserted between recorded samples in the timed loop.
|
||
// Mitigates CPU thermal-throttling drift across a sustained burst (e.g. 10×250ms = 2.5 sec):
|
||
// without it, boost-clock can drop mid-batch on thermally-constrained hosts (laptops esp.),
|
||
// and the later samples in the batch read systematically slower than the early ones. 50ms is
|
||
// enough for boost-clock state to settle but cheap in total (~500ms / cell) — quick-bench
|
||
// workflow is not meaningfully slower.
|
||
internal const int InterSampleSettleMs = 50;
|
||
|
||
// JIT-tier-promotion drain delay between warmup and measurement.
|
||
// - JIT mode (RuntimeFeature.IsDynamicCodeCompiled == true): tiered JIT promotes hot methods
|
||
// in a background thread; we wait briefly for the queue to drain so the first measurement
|
||
// sample doesn't catch a Tier-0 → Tier-1 transition mid-flight.
|
||
// - AOT mode (NativeAOT publish): no dynamic compilation happens; the sleep is pure noise.
|
||
// 250ms (vs the historical 3000ms) is sufficient for a few-method working set under .NET 9's
|
||
// tiered JIT — empirically the queue drains in <100ms for the bench's hot path.
|
||
internal static int JitSleep => RuntimeFeature.IsDynamicCodeCompiled ? 250 : 0;
|
||
|
||
// OptionsPreset values are passed per-instance (constructor argument), not constants —
|
||
// each CreateSerializers call line specifies its own preset name (e.g. "FastMode", "NoIntern").
|
||
|
||
internal static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false);
|
||
|
||
/// <summary>
|
||
/// Returns a human-readable name for the currently-active <c>BenchmarkTestDataProvider.LongStringSuffix</c>
|
||
/// charset. Returns "Custom" when the suffix doesn't match any of the predefined
|
||
/// <see cref="CharsetSuffixes"/> constants. Used in menu state display, console run header, and
|
||
/// the .LLM / .log output headers so per-charset bench files are self-documenting.
|
||
/// </summary>
|
||
internal static string GetCurrentCharsetName()
|
||
{
|
||
var s = BenchmarkTestDataProvider.LongStringSuffix;
|
||
|
||
return s switch
|
||
{
|
||
CharsetSuffixes.AsciiFix => nameof(CharsetSuffixes.AsciiFix),
|
||
CharsetSuffixes.AsciiShort => nameof(CharsetSuffixes.AsciiShort),
|
||
CharsetSuffixes.AsciiLong => nameof(CharsetSuffixes.AsciiLong),
|
||
CharsetSuffixes.Latin1Fix => nameof(CharsetSuffixes.Latin1Fix),
|
||
CharsetSuffixes.Latin1Short => nameof(CharsetSuffixes.Latin1Short),
|
||
CharsetSuffixes.Latin1Long => nameof(CharsetSuffixes.Latin1Long),
|
||
CharsetSuffixes.CjkBmpShort => nameof(CharsetSuffixes.CjkBmpShort),
|
||
CharsetSuffixes.CjkBmpLong => nameof(CharsetSuffixes.CjkBmpLong),
|
||
CharsetSuffixes.CyrillicShort => nameof(CharsetSuffixes.CyrillicShort),
|
||
CharsetSuffixes.CyrillicLong => nameof(CharsetSuffixes.CyrillicLong),
|
||
CharsetSuffixes.MixedShort => nameof(CharsetSuffixes.MixedShort),
|
||
CharsetSuffixes.MixedLong => nameof(CharsetSuffixes.MixedLong),
|
||
_ => "Custom"
|
||
};
|
||
}
|
||
}
|