using AyCode.Core.Serializers.Attributes; using AyCode.Core.Serializers.Binaries; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; namespace AyCode.Core.Serializers.Console; /// /// 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 Program.cs 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. /// internal static class Configuration { internal const string ResultsDirectory = @"H:\Applications\Aycode\Source\AyCode.Core\Test_Benchmark_Results\Benchmark"; #if AYCODE_NATIVEAOT internal const string BuildConfiguration = "NativeAOT"; #elif DEBUG internal const string BuildConfiguration = "Debug"; #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; // Serializer name constants // Engine identifiers (used in Engine column + comparison logic) internal const string EngineAcBinary = "AcBinary"; internal const string EngineMemoryPack = "MemoryPack"; #if !AYCODE_NATIVEAOT internal const string EngineMessagePack = "MessagePack"; #endif internal const string EngineSystemTextJson = "System.Text.Json"; // IO mode identifiers (used in IO column + comparison logic) internal const string IoByteArray = "Byte[]"; internal const string IoBufWrReuse = "BufWr reuse"; internal const string IoBufWrNew = "BufWr new"; internal const string IoString = "String"; internal const string IoNamedPipe = "NamedPipe"; internal const string IoNamedPipeRaw = "NamedPipe"; internal const string IoInMemoryPipe = "Pipe(in-mem)"; internal const string IoInMemoryRaw = "Pipe(in-mem)"; // 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; // Dispatch mode identifiers — describes how property access / type dispatch happens for a given run. // SGen = compile-time source generator path (Unsafe.As direct fields, slot-array wrapper lookup). // Runtime= reflection / compiled-delegate path. // Hybrid = SGen root with non-SGen child types reached via bridge methods. See docs/BINARY/BINARY_SGEN.md. internal const string ModeSGen = "SGen"; internal const string ModeRuntime = "Runtime"; internal const string ModeHybrid = "Hybrid"; // 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; // 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); /// /// Aggregated feature flags across every type tagged with /// the attribute in the loaded assemblies. Cached on first access (single reflection scan at startup). /// Used by the benchmark's per-row Options-column formatter so the column shows BOTH the configured /// options-level value AND the effective attribute-level enable flag — a feature flagged off at the /// type level overrides the options regardless of preset, and that asymmetry must surface in the log /// to avoid misreading a "RefHandling=OnlyId" / "Interning=All" line as actually active. /// Aggregation rule: if ALL tagged types have the feature enabled → true; if any tagged type /// disables it → false (a single disabling type suppresses the feature on the type-graph). /// internal static readonly (bool refHandling, bool internString, bool metadata, bool idTracking, bool propertyFilter) AttrFlags = ScanAttributeFlags(); private static (bool refHandling, bool internString, bool metadata, bool idTracking, bool propertyFilter) ScanAttributeFlags() { var attrs = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => { try { return a.GetTypes(); } catch { return Array.Empty(); } }) .Select(t => t.GetCustomAttribute()) .Where(a => a != null) .ToList(); if (attrs.Count == 0) return (false, false, false, false, false); return ( refHandling: attrs.All(a => a!.EnableRefHandlingFeature), internString: attrs.All(a => a!.EnableInternStringFeature), metadata: attrs.All(a => a!.EnableMetadataFeature), idTracking: attrs.All(a => a!.EnableIdTrackingFeature), propertyFilter: attrs.All(a => a!.EnablePropertyFilterFeature)); } }