AyCode.Core/AyCode.Core.Serializers.Con.../Configuration.cs

121 lines
6.8 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"
};
}
}