diff --git a/AyCode.Core.Serializers.Console/Configuration.cs b/AyCode.Core.Serializers.Console/Configuration.cs
new file mode 100644
index 0000000..e1b72d0
--- /dev/null
+++ b/AyCode.Core.Serializers.Console/Configuration.cs
@@ -0,0 +1,133 @@
+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));
+ }
+}
diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs
index 2b74bcc..61f31a1 100644
--- a/AyCode.Core.Serializers.Console/Program.cs
+++ b/AyCode.Core.Serializers.Console/Program.cs
@@ -32,120 +32,7 @@ namespace AyCode.Core.Serializers.Console;
///
public static class Program
{
- private const string ResultsDirectory = @"H:\Applications\Aycode\Source\AyCode.Core\Test_Benchmark_Results\Benchmark";
-
-#if AYCODE_NATIVEAOT
- private const string BuildConfiguration = "NativeAOT";
-#elif DEBUG
- private const string BuildConfiguration = "Debug";
-#else
- private const string BuildConfiguration = "Release";
-#endif
-
-#if DEBUG
- private static int WarmupIterations = 0;
- private static int TestIterations = 1;
- private static int BenchmarkSamples = 1; // Debug: single sample, fast iteration
-#else
- private static int WarmupIterations = 5000; //10000 — per-phase (Ser AND Des get their own warmup separately)
- private static int TestIterations = 1000; //1000
- private static int BenchmarkSamples = 10;
-#endif
-
- // Interactive settings: selected AcBinary wire mode for benchmark runs.
- // 1 = Compact, 2 = Fast
- private static WireMode SelectedWireMode = WireMode.Compact;
-
- // Serializer name constants
- // Engine identifiers (used in Engine column + comparison logic)
- private const string EngineAcBinary = "AcBinary";
- private const string EngineMemoryPack = "MemoryPack";
-#if !AYCODE_NATIVEAOT
- private const string EngineMessagePack = "MessagePack";
-#endif
- private const string EngineSystemTextJson = "System.Text.Json";
-
- // IO mode identifiers (used in IO column + comparison logic)
- private const string IoByteArray = "Byte[]";
- private const string IoBufWrReuse = "BufWr reuse";
- private const string IoBufWrNew = "BufWr new";
- private const string IoString = "String";
- private const string IoNamedPipe = "NamedPipe";
- private const string IoNamedPipeRaw = "NamedPipe";
- private const string IoInMemoryPipe = "Pipe(in-mem)";
- private 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.
- private 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.
- private const string ModeSGen = "SGen";
- private const string ModeRuntime = "Runtime";
- private 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.
- private 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.
- private 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.
- private 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").
-
- private 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 so the per-row Options 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).
- ///
- private static readonly (bool refHandling, bool internString, bool metadata, bool idTracking, bool propertyFilter) _attrFlags
- = ScanAcBinaryAttributeFlags();
-
- private static (bool refHandling, bool internString, bool metadata, bool idTracking, bool propertyFilter) ScanAcBinaryAttributeFlags()
- {
- 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));
- }
+ // Configuration (constants, mutable state, attribute-flag aggregation) → Configuration.cs
///
/// Common Options-column formatter for every AcBinary serializer benchmark row. Renders the
@@ -163,16 +50,16 @@ public static class Program
var propFilterOpt = options.PropertyFilter == null ? "None" : "Set";
return $"WireMode={options.WireMode}, " +
- $"RefHandling={options.ReferenceHandling}(opt) | {_attrFlags.refHandling} (attr), " +
- $"Interning={options.UseStringInterning}(opt) | {_attrFlags.internString} (attr), " +
- $"Metadata={options.UseMetadata}(opt) | {_attrFlags.metadata} (attr), " +
- $"PropertyFilter={propFilterOpt}(opt) | {_attrFlags.propertyFilter} (attr), " +
+ $"RefHandling={options.ReferenceHandling}(opt) | {Configuration.AttrFlags.refHandling} (attr), " +
+ $"Interning={options.UseStringInterning}(opt) | {Configuration.AttrFlags.internString} (attr), " +
+ $"Metadata={options.UseMetadata}(opt) | {Configuration.AttrFlags.metadata} (attr), " +
+ $"PropertyFilter={propFilterOpt}(opt) | {Configuration.AttrFlags.propertyFilter} (attr), " +
$"SGen={options.UseGeneratedCode}, " +
$"Compression={options.UseCompression}{extra}";
}
///
- /// Returns MemoryPack serializer options aligned with for a fair
+ /// Returns MemoryPack serializer options aligned with for a fair
/// apples-to-apples wire-format comparison:
///
/// - → (UTF-8) — both
@@ -185,15 +72,15 @@ public static class Program
/// the encoding-family difference, NOT an AcBinary-specific overhead.
///
private static MemoryPackSerializerOptions GetMemPackOptions() =>
- SelectedWireMode == WireMode.Fast
+ Configuration.SelectedWireMode == WireMode.Fast
? MemoryPackSerializerOptions.Utf16
: MemoryPackSerializerOptions.Default;
///
- /// Converts a total-time (in ms across ) into per-operation microseconds.
+ /// Converts a total-time (in ms across ) into per-operation microseconds.
/// Formula: totalMs / iterations × 1000. The benchmark stores *TimeMs as the cumulative
/// median over the timing run; the display layer renders per-op µs to make numbers iteration-count
- /// independent (e.g. switching TestIterations 1000 → 100 leaves the displayed µs/op unchanged
+ /// independent (e.g. switching Configuration.TestIterations 1000 → 100 leaves the displayed µs/op unchanged
/// — only its sample noise grows). Symmetric with the already-per-op *AllocBytesPerOp fields.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -202,7 +89,7 @@ public static class Program
/// Converts a total-time (in ms across ) into per-operation microseconds.
/// Per-op µs is the iter-independent unit: 1000 iter and 50000 iter of the same operation should
/// produce the same per-op µs (within noise). Necessary because per-cell adaptive iteration makes
- /// iterations a per-row property — there is no longer a single global TestIterations to divide by.
+ /// iterations a per-row property — there is no longer a single global Configuration.TestIterations to divide by.
///
private static double ToPerOpMicros(double totalMs, int iterations) => iterations > 0 ? totalMs / iterations * 1000.0 : 0;
@@ -271,7 +158,7 @@ public static class Program
///
/// Formats a per-op micros value with its inter-sample range and CV-threshold marker as
/// "26.86 (24.5..29.1)" or "26.86 (24.5..29.1) ⚠️5.2%". Median first, range in parentheses,
- /// CV warning suffix only when CV > . When min == max == median
+ /// CV warning suffix only when CV > . When min == max == median
/// (single-sample / Debug / quick mode), collapses to bare median to avoid visual clutter.
/// All time inputs are total-batch milliseconds; is the per-row iter
/// count (post-adaptive-calibration).
@@ -292,7 +179,7 @@ public static class Program
if (medianMs > 0 && stdDevMs > 0)
{
var cv = stdDevMs / medianMs;
- if (cv > UnstableCVThreshold)
+ if (cv > Configuration.UnstableCVThreshold)
{
var cvPct = (cv * 100).ToString("F1", inv);
return $"{range} ⚠️{cvPct}%";
@@ -324,7 +211,7 @@ public static class Program
if (args.Length > 0)
{
if (!TryParseCliArgs(args, out var layer, out var opMode, out var serializerMode))
- return; // profiler mode (already ran) or invalid args
+ return; // invalid args
RunBenchmark(layer, opMode, serializerMode);
return;
@@ -350,8 +237,7 @@ public static class Program
///
/// Parses CLI arguments into (layer, opMode, serializerMode). Returns false if the args
- /// indicate a special mode that has already been handled (e.g. profiler) or are invalid;
- /// the caller should then exit without running the standard benchmark.
+ /// are invalid; the caller should then exit without running the standard benchmark.
///
private static bool TryParseCliArgs(string[] args, out string layer, out string opMode, out string serializerMode)
{
@@ -361,19 +247,12 @@ public static class Program
var arg = args[0].ToLower();
- // Profiler mode: warmup only, then exit (for memory profiler analysis)
- if (arg == "profiler")
- {
- RunProfilerMode();
- return false;
- }
-
// Quick mode: short warmup, few iterations, small sample count
if (arg == "quick")
{
- WarmupIterations = 5;
- TestIterations = 100;
- BenchmarkSamples = 3;
+ Configuration.WarmupIterations = 5;
+ Configuration.TestIterations = 100;
+ Configuration.BenchmarkSamples = 3;
layer = "all";
}
else if (arg is "core" or "comprehensive" or "edge" or "all"
@@ -424,7 +303,7 @@ public static class Program
// randomly inflate samples by 5-15%.
// Try/finally guarantees the original state is restored even if a benchmark throws — leaving
// a developer machine pinned to one core after a crashed run is a real foot-gun.
- // Skipped on Debug single-sample mode (BenchmarkSamples <= 1) where stabilization is moot.
+ // Skipped on Debug single-sample mode (Configuration.BenchmarkSamples <= 1) where stabilization is moot.
var process = Process.GetCurrentProcess();
var origAffinity = (IntPtr)0;
var origPriority = ProcessPriorityClass.Normal;
@@ -433,7 +312,7 @@ public static class Program
// ProcessorAffinity is only supported on Windows + Linux (CA1416). macOS would throw at
// runtime; skip the affinity step there but still raise priority class (which IS supported
// on macOS, just less effective for stabilization than affinity pinning).
- if (BenchmarkSamples > 1 && (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()))
+ if (Configuration.BenchmarkSamples > 1 && (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()))
{
try
{
@@ -465,8 +344,8 @@ public static class Program
var allTestDataSets = BenchmarkTestDataProvider.CreateTestDataSets();
var testDataSets = FilterByLayer(allTestDataSets, layer);
- System.Console.WriteLine($"Layer: {layer} | OpMode: {opMode} | SerializerMode: {serializerMode} | Charset: {GetCurrentCharsetName()} | Iterations: per-cell adaptive (~{TargetSampleMs} ms target) | Warmup: {WarmupIterations} per phase (Ser/Des isolated) | Samples: {BenchmarkSamples} (median) + pilot discard");
- System.Console.WriteLine($"Build: {BuildConfiguration} | .NET: {Environment.Version} | Test Type: {testDataSets.FirstOrDefault()?.TypeName ?? "unknown"} | Test Cells: {testDataSets.Count}/{allTestDataSets.Count}");
+ System.Console.WriteLine($"Layer: {layer} | OpMode: {opMode} | SerializerMode: {serializerMode} | Charset: {GetCurrentCharsetName()} | Iterations: per-cell adaptive (~{Configuration.TargetSampleMs} ms target) | Warmup: {Configuration.WarmupIterations} per phase (Ser/Des isolated) | Samples: {Configuration.BenchmarkSamples} (median) + pilot discard");
+ System.Console.WriteLine($"Build: {Configuration.BuildConfiguration} | .NET: {Environment.Version} | Test Type: {testDataSets.FirstOrDefault()?.TypeName ?? "unknown"} | Test Cells: {testDataSets.Count}/{allTestDataSets.Count}");
System.Console.WriteLine();
// Global JIT pre-warmup — touches every (testdata × serializer) code path BEFORE any timing happens.
@@ -476,7 +355,7 @@ public static class Program
// on later cells (e.g. Small BufWr reuse 9ms vs Medium BufWr reuse 4ms — even though Medium is bigger).
// Pre-warmup runs every overload at least once with each data shape so .NET 9's tiered JIT promotes
// them all in the background; the per-cell warmup that follows then locks in cache + branch state.
- if (BenchmarkSamples > 1) // skip in DEBUG (single-sample fast iteration)
+ if (Configuration.BenchmarkSamples > 1) // skip in DEBUG (single-sample fast iteration)
{
System.Console.WriteLine($"Global JIT pre-warmup ({testDataSets.Count} cells × all serializers, light pass)...");
@@ -500,7 +379,7 @@ public static class Program
}
// Let background tiered-JIT compilation drain before we begin measuring.
- if (JitSleep > 0) Thread.Sleep(JitSleep);
+ if (Configuration.JitSleep > 0) Thread.Sleep(Configuration.JitSleep);
System.Console.WriteLine("✓ Global pre-warmup complete.\n");
}
@@ -536,62 +415,6 @@ public static class Program
}
}
- ///
- /// Profiler mode: warmup only, then EXIT immediately.
- /// Usage: dotnet run -- profiler
- ///
- private static void RunProfilerMode()
- {
- System.Console.WriteLine("╔══════════════════════════════════════════════════════════════════════╗");
- System.Console.WriteLine("║ PROFILER MODE (AcBinary only) ║");
- System.Console.WriteLine("╚══════════════════════════════════════════════════════════════════════╝");
- System.Console.WriteLine($"Build: {BuildConfiguration} | .NET: {Environment.Version}");
- System.Console.WriteLine();
-
- var order = BenchmarkTestDataProvider.CreateProfilerOrder();
-
- var options = AcBinarySerializerOptions.WithoutReferenceHandling;
- options.UseStringInterning = StringInterningMode.None;
-
- var bytes = AcBinarySerializer.Serialize(order, options);
-
- // Warmup (fills caches)
- System.Console.WriteLine("Warming up (1000 iterations)...");
- for (var i = 0; i < 1000; i++)
- {
- _ = AcBinarySerializer.Serialize(order, options);
- _ = AcBinaryDeserializer.Deserialize(bytes);
- }
-
- Thread.Sleep(2000);
- System.Console.WriteLine("Warmup complete. Caches are now populated.");
- System.Console.WriteLine();
-
- // HOT PATH - this is what the profiler should capture!
- System.Console.WriteLine("Running hot path serialization (1000 iterations for profiling)...");
- for (var i = 0; i < 1000; i++)
- {
- _ = AcBinarySerializer.Serialize(order, options);
- //_ = AcBinaryDeserializer.Deserialize(bytes);
- }
-
- System.Console.WriteLine("Running hot path deserialization (1000 iterations for profiling)...");
- for (var i = 0; i < 1000; i++)
- {
- _ = AcBinaryDeserializer.Deserialize(bytes);
- }
-
- System.Console.WriteLine("Hot path complete.");
- System.Console.WriteLine();
-
- System.Console.WriteLine(">>> ATTACH MEMORY PROFILER NOW <<<");
- System.Console.WriteLine("Press any key to exit...");
- System.Console.ReadKey(intercept: true);
-
- System.Console.WriteLine();
- System.Console.WriteLine("✓ Profiler mode complete. Exiting now.");
- }
-
#region Benchmark Execution
///
@@ -644,9 +467,9 @@ public static class Program
// pool churn from Ser, deserialized object graph from Des) so the next phase starts with a quiescent
// heap — GC tier-promotion timing during measurement is then driven only by THAT phase's allocations.
//
- // JitSleep per-phase: tiered JIT background promotion drain after each warmup (mode-aware: 0 ms in AOT).
+ // Configuration.JitSleep per-phase: tiered JIT background promotion drain after each warmup (mode-aware: 0 ms in AOT).
// Each phase's freshly-promoted methods settle before its timing starts.
- System.Console.WriteLine($"Running benchmarks (target ~{TargetSampleMs} ms/sample × {BenchmarkSamples} samples median, phase-isolated warmup/measure per Ser/Des)...\n");
+ System.Console.WriteLine($"Running benchmarks (target ~{Configuration.TargetSampleMs} ms/sample × {Configuration.BenchmarkSamples} samples median, phase-isolated warmup/measure per Ser/Des)...\n");
foreach (var serializer in serializers)
{
@@ -676,10 +499,10 @@ public static class Program
if (mode is "all" or "serialize" or "ser")
{
ForceGcCollect();
- serializer.WarmupSerialize(WarmupIterations);
- if (JitSleep > 0) Thread.Sleep(JitSleep);
+ serializer.WarmupSerialize(Configuration.WarmupIterations);
+ if (Configuration.JitSleep > 0) Thread.Sleep(Configuration.JitSleep);
- var rtIter = CalibrateIterations(() => serializer.Serialize(), TargetSampleMs);
+ var rtIter = CalibrateIterations(() => serializer.Serialize(), Configuration.TargetSampleMs);
var (rtMed, rtMin, rtMax, rtStd) = RunTimed(() => serializer.Serialize(), rtIter, $"{groupLabel} [RT timing]");
result.RoundTripTimeMs = rtMed;
result.RoundTripTimeMinMs = rtMin;
@@ -694,14 +517,14 @@ public static class Program
}
else
{
- // ── Ser phase ── isolated warmup → JitSleep → calibrate → time → alloc; preceded by GC.Collect.
+ // ── Ser phase ── isolated warmup → Configuration.JitSleep → calibrate → time → alloc; preceded by GC.Collect.
if (mode is "all" or "serialize" or "ser")
{
ForceGcCollect();
- serializer.WarmupSerialize(WarmupIterations);
- if (JitSleep > 0) Thread.Sleep(JitSleep);
+ serializer.WarmupSerialize(Configuration.WarmupIterations);
+ if (Configuration.JitSleep > 0) Thread.Sleep(Configuration.JitSleep);
- var serIter = CalibrateIterations(() => serializer.Serialize(), TargetSampleMs);
+ var serIter = CalibrateIterations(() => serializer.Serialize(), Configuration.TargetSampleMs);
var (serMed, serMin, serMax, serStd) = RunTimed(() => serializer.Serialize(), serIter, $"{groupLabel} [Ser timing]");
result.SerializeTimeMs = serMed;
result.SerializeTimeMinMs = serMin;
@@ -712,16 +535,16 @@ public static class Program
result.SerializeAllocBytesPerOp = MeasureAllocation(() => serializer.Serialize(), serIter, $"{groupLabel} [Ser alloc]");
}
- // ── Des phase ── isolated warmup → JitSleep → calibrate → time → alloc; preceded by GC.Collect.
+ // ── Des phase ── isolated warmup → Configuration.JitSleep → calibrate → time → alloc; preceded by GC.Collect.
// The GC.Collect here is critical: it discards the Ser-phase's write-buffer pool churn so the
// Des-phase's allocation measurement reflects ONLY Des-side allocations (deserialized object graph).
if (mode is "all" or "deserialize" or "des")
{
ForceGcCollect();
- serializer.WarmupDeserialize(WarmupIterations);
- if (JitSleep > 0) Thread.Sleep(JitSleep);
+ serializer.WarmupDeserialize(Configuration.WarmupIterations);
+ if (Configuration.JitSleep > 0) Thread.Sleep(Configuration.JitSleep);
- var desIter = CalibrateIterations(() => serializer.Deserialize(), TargetSampleMs);
+ var desIter = CalibrateIterations(() => serializer.Deserialize(), Configuration.TargetSampleMs);
var (desMed, desMin, desMax, desStd) = RunTimed(() => serializer.Deserialize(), desIter, $"{groupLabel} [Des timing]");
result.DeserializeTimeMs = desMed;
result.DeserializeTimeMinMs = desMin;
@@ -767,7 +590,7 @@ public static class Program
if (serializerMode == "fastestbyte")
{
var fastestByteOptions = AcBinarySerializerOptions.FastMode;
- fastestByteOptions.WireMode = SelectedWireMode;
+ fastestByteOptions.WireMode = Configuration.SelectedWireMode;
return new List
{
@@ -790,8 +613,8 @@ public static class Program
// fits blocking-free in one kernel pipe-buffer slot. Single source of truth for both app-level
// wire chunk AND kernel transfer unit; change ONLY this line when tuning.
var binaryFastModePipeChunkOnly = AcBinarySerializerOptions.FastMode;
- binaryFastModePipeChunkOnly.BufferWriterChunkSize = PipeChunkSize;
- binaryFastModePipeChunkOnly.WireMode = SelectedWireMode;
+ binaryFastModePipeChunkOnly.BufferWriterChunkSize = Configuration.PipeChunkSize;
+ binaryFastModePipeChunkOnly.WireMode = Configuration.SelectedWireMode;
return new List
{
@@ -826,18 +649,18 @@ public static class Program
var binaryNoInternOption = AcBinarySerializerOptions.Default;
binaryNoInternOption.UseStringInterning = StringInterningMode.None;
- binaryNoInternOption.WireMode = SelectedWireMode;
+ binaryNoInternOption.WireMode = Configuration.SelectedWireMode;
var binaryDefaultNoSgenOption = AcBinarySerializerOptions.Default;
binaryDefaultNoSgenOption.UseGeneratedCode = false;
- binaryDefaultNoSgenOption.WireMode = SelectedWireMode;
+ binaryDefaultNoSgenOption.WireMode = Configuration.SelectedWireMode;
var binaryFastModeNoSgenOption = AcBinarySerializerOptions.FastMode;
binaryFastModeNoSgenOption.UseGeneratedCode = false;
- binaryFastModeNoSgenOption.WireMode = SelectedWireMode;
+ binaryFastModeNoSgenOption.WireMode = Configuration.SelectedWireMode;
var binaryFastModeOption = AcBinarySerializerOptions.FastMode;
- binaryFastModeOption.WireMode = SelectedWireMode;
+ binaryFastModeOption.WireMode = Configuration.SelectedWireMode;
// BufWr new — 4 KB chunk size for the FRESH ArrayBufferWriter scenario. The chunkSize here drives
// the serializer's GetSpan(N) request → the ArrayBufferWriter's internal allocation per call.
@@ -845,20 +668,20 @@ public static class Program
// allocates a fresh ABW. Independent of the AsyncPipe profile (different mechanism: alloc overhead
// vs syscall count).
var binaryFastModeBufWrChunk = AcBinarySerializerOptions.FastMode;
- binaryFastModeBufWrChunk.BufferWriterChunkSize = PipeChunkSize;
- binaryFastModeBufWrChunk.WireMode = SelectedWireMode;
+ binaryFastModeBufWrChunk.BufferWriterChunkSize = Configuration.PipeChunkSize;
+ binaryFastModeBufWrChunk.WireMode = Configuration.SelectedWireMode;
// In-memory Pipe variant — same 4 KB chunkSize as the AsyncPipe mode, no kernel-pipe alignment
// concern (managed slabs are not page-aligned anyway). Drives SerializeChunkedFramed via the in-memory
// System.IO.Pipelines.Pipe (zero-copy slab handoff between producer and drain task).
var binaryFastModePipeChunkInMem = AcBinarySerializerOptions.FastMode;
- binaryFastModePipeChunkInMem.BufferWriterChunkSize = PipeChunkSize;
- binaryFastModePipeChunkInMem.WireMode = SelectedWireMode;
+ binaryFastModePipeChunkInMem.BufferWriterChunkSize = Configuration.PipeChunkSize;
+ binaryFastModePipeChunkInMem.WireMode = Configuration.SelectedWireMode;
var defaultOptions = AcBinarySerializerOptions.Default;
defaultOptions.UseStringInterning = StringInterningMode.None;
defaultOptions.ReferenceHandling = ReferenceHandlingMode.OnlyId;
- defaultOptions.WireMode = SelectedWireMode;
+ defaultOptions.WireMode = Configuration.SelectedWireMode;
return new List
{
@@ -927,10 +750,10 @@ public static class Program
}
///
- /// Runs the action times for independent samples,
+ /// Runs the action times for independent samples,
/// returning the median, min, and max elapsed time. Multi-sample design reduces single-run variance
/// from ~±15% to ~±5% by smoothing transient effects (background activity, thermal/turbo state).
- /// When <= 1, falls back to single-sample timing (Debug / quick mode).
+ /// When <= 1, falls back to single-sample timing (Debug / quick mode).
/// When is non-null, emits in-place \r progress updates so a
/// stuck benchmark (e.g. deadlocked NamedPipe row) is visibly stuck at a specific %% rather than
/// silently hanging.
@@ -948,7 +771,7 @@ public static class Program
///
private static (double medianMs, double minMs, double maxMs, double stdDevMs) RunTimed(Action action, int iterations, string? progressLabel = null)
{
- var samples = BenchmarkSamples;
+ var samples = Configuration.BenchmarkSamples;
if (samples <= 1)
{
// Single-sample fast path (Debug or trivial run) — no allocation, no sort, no stddev.
@@ -1018,16 +841,16 @@ public static class Program
///
/// Per-cell adaptive iteration calibration. Runs a 100-iter measurement after warmup and computes
- /// how many iterations are needed to reach wall-clock per sample.
+ /// how many iterations are needed to reach wall-clock per sample.
/// Returns iter rounded UP to the nearest 1000, floored at 1000 (the prior fixed minimum) and
/// ceiling-capped at 200_000 (sanity bound for pathologically fast ops). In Debug single-sample mode
- /// (BenchmarkSamples <= 1) returns the global unchanged —
+ /// (Configuration.BenchmarkSamples <= 1) returns the global unchanged —
/// calibration overhead is unjustified there. Calibration runs OUTSIDE the timed sample loop and
/// does NOT count toward warmup; its sole purpose is to measure per-op cost.
///
private static int CalibrateIterations(Action action, int targetMs)
{
- if (BenchmarkSamples <= 1) return TestIterations; // Debug fast path
+ if (Configuration.BenchmarkSamples <= 1) return Configuration.TestIterations; // Debug fast path
GC.Collect();
GC.WaitForPendingFinalizers();
@@ -1244,7 +1067,7 @@ public static class Program
System.Console.WriteLine(" [A] All layers");
System.Console.WriteLine(" [F] FastestByte — AcBinary FastMode Byte[] vs MemoryPack Byte[] only (tight optimization loop)");
System.Console.WriteLine(" [P] AsyncPipe — streaming I/O isolation (only AsyncPipe, all test data)");
- System.Console.WriteLine($" [S] Settings — Iteration / WireMode (current: {SelectedWireMode})");
+ System.Console.WriteLine($" [S] Settings — Iteration / WireMode (current: {Configuration.SelectedWireMode})");
System.Console.WriteLine(" [Q] Quit");
System.Console.Write("\nSelection: ");
@@ -1270,7 +1093,7 @@ public static class Program
///
/// Settings sub-menu — prompts for Warmup / Iterations / Samples values. Empty input keeps the
- /// current value. Validation: WarmupIterations ≥ 0; TestIterations ≥ 1; BenchmarkSamples ≥ 1.
+ /// current value. Validation: Configuration.WarmupIterations ≥ 0; Configuration.TestIterations ≥ 1; Configuration.BenchmarkSamples ≥ 1.
/// Returns to the caller (which re-displays the main menu).
///
private static void ShowSettingsMenu()
@@ -1282,7 +1105,7 @@ public static class Program
System.Console.WriteLine("Settings");
System.Console.WriteLine("─────────────────────────────────────────────");
System.Console.WriteLine(" [1] Iteration — Warmup / Iterations / Samples");
- System.Console.WriteLine($" [2] WireMode — current: {SelectedWireMode}");
+ System.Console.WriteLine($" [2] WireMode — current: {Configuration.SelectedWireMode}");
System.Console.WriteLine($" [3] Charset — current: {GetCurrentCharsetName()}");
System.Console.WriteLine(" [B] Back");
System.Console.Write("\nSelection: ");
@@ -1395,12 +1218,12 @@ public static class Program
System.Console.WriteLine("─────────────────────────────────────────────");
System.Console.WriteLine();
- WarmupIterations = PromptInt("WarmupIterations", WarmupIterations, min: 0);
- TestIterations = PromptInt("TestIterations ", TestIterations, min: 1);
- BenchmarkSamples = PromptInt("BenchmarkSamples", BenchmarkSamples, min: 1);
+ Configuration.WarmupIterations = PromptInt("Configuration.WarmupIterations", Configuration.WarmupIterations, min: 0);
+ Configuration.TestIterations = PromptInt("Configuration.TestIterations ", Configuration.TestIterations, min: 1);
+ Configuration.BenchmarkSamples = PromptInt("Configuration.BenchmarkSamples", Configuration.BenchmarkSamples, min: 1);
System.Console.WriteLine();
- System.Console.WriteLine($"✓ Iteration settings updated: Warmup={WarmupIterations} | Iterations={TestIterations} | Samples={BenchmarkSamples}");
+ System.Console.WriteLine($"✓ Iteration settings updated: Warmup={Configuration.WarmupIterations} | Iterations={Configuration.TestIterations} | Samples={Configuration.BenchmarkSamples}");
}
private static void ShowWireModeSettingsMenu()
@@ -1411,7 +1234,7 @@ public static class Program
System.Console.WriteLine("─────────────────────────────────────────────");
System.Console.WriteLine("WireMode settings");
System.Console.WriteLine("─────────────────────────────────────────────");
- System.Console.WriteLine($"Current: {SelectedWireMode}");
+ System.Console.WriteLine($"Current: {Configuration.SelectedWireMode}");
System.Console.WriteLine(" [1] Compact");
System.Console.WriteLine(" [2] Fast");
System.Console.WriteLine(" [B] Back");
@@ -1423,11 +1246,11 @@ public static class Program
switch (char.ToLower(key))
{
case '1':
- SelectedWireMode = WireMode.Compact;
+ Configuration.SelectedWireMode = WireMode.Compact;
System.Console.WriteLine("✓ WireMode set to Compact");
return;
case '2':
- SelectedWireMode = WireMode.Fast;
+ Configuration.SelectedWireMode = WireMode.Fast;
System.Console.WriteLine("✓ WireMode set to Fast");
return;
case 'b':
@@ -1515,8 +1338,8 @@ public static class Program
/// rankings (because both metrics are misleading there) — they still participate in "Fastest Round-trip".
/// Default false for in-memory IO modes which measure Ser and Des separately.
bool IsRoundTripOnly => false;
- /// Combined warmup (Ser + Deser interleaved). Kept for backward-compat with ProfilerMode
- /// and other callers that don't need phase-separated warmup. The benchmark loop prefers the split
+ /// Combined warmup (Ser + Deser interleaved). Currently unused — kept as a legacy entry point
+ /// for any external caller that still wants single-call warmup. The benchmark loop uses the split
/// + pair for cache-isolated measurements.
void Warmup(int iterations);
@@ -1549,9 +1372,9 @@ public static class Program
private readonly AcBinarySerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => EngineAcBinary;
- public string IoMode => IoByteArray;
- public string DispatchMode => _options.UseGeneratedCode ? ModeSGen : ModeRuntime;
+ public string Engine => Configuration.EngineAcBinary;
+ public string IoMode => Configuration.IoByteArray;
+ public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
@@ -1606,9 +1429,9 @@ public static class Program
private readonly MemoryPackSerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => EngineMemoryPack;
- public string IoMode => IoByteArray;
- public string DispatchMode => ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
+ public string Engine => Configuration.EngineMemoryPack;
+ public string IoMode => Configuration.IoByteArray;
+ public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
@@ -1657,9 +1480,9 @@ public static class Program
private readonly MessagePackSerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => EngineMessagePack;
- public string IoMode => IoByteArray;
- public string DispatchMode => ModeSGen; // MessagePack uses [MessagePackObject] source-generated formatters (StandardResolver)
+ public string Engine => Configuration.EngineMessagePack;
+ public string IoMode => Configuration.IoByteArray;
+ public string DispatchMode => Configuration.ModeSGen; // MessagePack uses [MessagePackObject] source-generated formatters (StandardResolver)
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
@@ -1722,9 +1545,9 @@ public static class Program
private readonly AcBinarySerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => EngineAcBinary;
- public string IoMode => IoBufWrNew;
- public string DispatchMode => _options.UseGeneratedCode ? ModeSGen : ModeRuntime;
+ public string Engine => Configuration.EngineAcBinary;
+ public string IoMode => Configuration.IoBufWrNew;
+ public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
@@ -1830,9 +1653,9 @@ public static class Program
private bool _captureResult; // toggle: when true, ConsumeLoop stores result; otherwise discards
private bool _disposed;
- public string Engine => EngineAcBinary;
- public string IoMode => IoNamedPipe;
- public string DispatchMode => _options.UseGeneratedCode ? ModeSGen : ModeRuntime;
+ public string Engine => Configuration.EngineAcBinary;
+ public string IoMode => Configuration.IoNamedPipe;
+ public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
@@ -2053,9 +1876,9 @@ public static class Program
private bool _captureResult;
private bool _disposed;
- public string Engine => EngineAcBinary;
- public string IoMode => IoInMemoryPipe;
- public string DispatchMode => _options.UseGeneratedCode ? ModeSGen : ModeRuntime;
+ public string Engine => Configuration.EngineAcBinary;
+ public string IoMode => Configuration.IoInMemoryPipe;
+ public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
@@ -2244,9 +2067,9 @@ public static class Program
private bool _captureResult; // toggle: when true, ConsumerLoop stores result; otherwise discards
private bool _disposed;
- public string Engine => EngineAcBinary;
- public string IoMode => IoNamedPipeRaw;
- public string DispatchMode => _options.UseGeneratedCode ? ModeSGen : ModeRuntime;
+ public string Engine => Configuration.EngineAcBinary;
+ public string IoMode => Configuration.IoNamedPipeRaw;
+ public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
@@ -2449,9 +2272,9 @@ public static class Program
private bool _captureResult;
private bool _disposed;
- public string Engine => EngineAcBinary;
- public string IoMode => IoInMemoryRaw;
- public string DispatchMode => _options.UseGeneratedCode ? ModeSGen : ModeRuntime;
+ public string Engine => Configuration.EngineAcBinary;
+ public string IoMode => Configuration.IoInMemoryRaw;
+ public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
@@ -2593,9 +2416,9 @@ public static class Program
private readonly MemoryPackSerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => EngineMemoryPack;
- public string IoMode => IoBufWrNew;
- public string DispatchMode => ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
+ public string Engine => Configuration.EngineMemoryPack;
+ public string IoMode => Configuration.IoBufWrNew;
+ public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
@@ -2647,9 +2470,9 @@ public static class Program
private readonly byte[] _serialized;
private readonly ArrayBufferWriter _bufferWriter;
- public string Engine => EngineAcBinary;
- public string IoMode => IoBufWrReuse;
- public string DispatchMode => _options.UseGeneratedCode ? ModeSGen : ModeRuntime;
+ public string Engine => Configuration.EngineAcBinary;
+ public string IoMode => Configuration.IoBufWrReuse;
+ public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
@@ -2718,9 +2541,9 @@ public static class Program
private readonly byte[] _serialized;
private readonly ArrayBufferWriter _bufferWriter;
- public string Engine => EngineMemoryPack;
- public string IoMode => IoBufWrReuse;
- public string DispatchMode => ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
+ public string Engine => Configuration.EngineMemoryPack;
+ public string IoMode => Configuration.IoBufWrReuse;
+ public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
@@ -2779,9 +2602,9 @@ public static class Program
private readonly string _serialized;
private readonly byte[] _serializedUtf8;
- public string Engine => EngineSystemTextJson;
- public string IoMode => IoString;
- public string DispatchMode => ModeRuntime; // System.Text.Json default uses reflection-based metadata (no source generator opt-in here)
+ public string Engine => Configuration.EngineSystemTextJson;
+ public string IoMode => Configuration.IoString;
+ public string DispatchMode => Configuration.ModeRuntime; // System.Text.Json default uses reflection-based metadata (no source generator opt-in here)
public string OptionsPreset { get; }
public int SerializedSize => _serializedUtf8.Length;
public long SetupSerializeAllocBytes => 0;
@@ -2798,7 +2621,7 @@ public static class Program
ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles
};
_serialized = JsonSerializer.Serialize(order, _options);
- _serializedUtf8 = Utf8NoBom.GetBytes(_serialized);
+ _serializedUtf8 = Configuration.Utf8NoBom.GetBytes(_serialized);
}
public void Warmup(int iterations)
@@ -2854,11 +2677,11 @@ public static class Program
public double DeserializeTimeMinMs { get; set; }
public double DeserializeTimeMaxMs { get; set; }
// Sample-population stddev (ms). Used by FormatMicrosWithRange to compute CV (stddev/mean)
- // and emit the ⚠️ marker on rows above UnstableCVThreshold. 0 in single-sample mode.
+ // and emit the ⚠️ marker on rows above Configuration.UnstableCVThreshold. 0 in single-sample mode.
public double SerializeTimeStdDevMs { get; set; }
public double DeserializeTimeStdDevMs { get; set; }
// Per-row adaptive iteration count (post-CalibrateIterations). Each Ser and Des function calibrates
- // independently to land its sample window at ~TargetSampleMs; per-op µs is then iter-independent
+ // independently to land its sample window at ~Configuration.TargetSampleMs; per-op µs is then iter-independent
// (`SerializeTimeMs / SerializeIterations * 1000`). For round-trip-only rows (NamedPipe etc.),
// RoundTripIterations carries the calibrated iter count; SerializeIterations and DeserializeIterations
// stay 0 (Ser and Des are not separately measurable on those rows).
@@ -2923,10 +2746,10 @@ public static class Program
// Order by per-op µs (iter-independent) — rows may have different iter counts post-calibration.
var testResults = results.Where(r => r.TestDataName == testData.DisplayName).OrderBy(r => RtPerOp(r)).ToList();
// Baseline switched MessagePack → MemoryPack: MemoryPack is the SOTA performance leader.
- var memPackResult = testResults.FirstOrDefault(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray));
+ var memPackResult = testResults.FirstOrDefault(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray));
// Pin the comparison to AcBinary's SGen variant — apples-to-apples vs MemoryPack (also source-generated).
// The Runtime variant is shown alongside in the table for context, not used as the headline number.
- var acBinaryResult = testResults.FirstOrDefault(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen));
+ var acBinaryResult = testResults.FirstOrDefault(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen));
System.Console.WriteLine($"\n┌─ {testData.DisplayName} ─".PadRight(172, '─') + "┐");
// Header-only units; per-row entries are numbers (µs/op for time, KB/op for alloc, KB pair "ser / des" for Setup, B for Size).
@@ -2947,8 +2770,8 @@ public static class Program
// Highlight MemoryPack baseline (any Byte[]) and AcBinary headline contender (Byte[] + SGen) with win/lose colors.
// The AcBinary Byte[]+Runtime variant is shown unhighlighted — it's contextual (SGen speed-up reference), not the headline.
- var isHighlighted = (result.Engine == EngineMemoryPack && result.IoMode == IoByteArray)
- || (result.Engine == EngineAcBinary && result.IoMode == IoByteArray && result.DispatchMode == ModeSGen);
+ var isHighlighted = (result.Engine == Configuration.EngineMemoryPack && result.IoMode == Configuration.IoByteArray)
+ || (result.Engine == Configuration.EngineAcBinary && result.IoMode == Configuration.IoByteArray && result.DispatchMode == Configuration.ModeSGen);
var prefix = isHighlighted ? "│►" : "│ ";
var suffix = isHighlighted ? "◄│" : " │";
@@ -2956,7 +2779,7 @@ public static class Program
// Color logic: Green = winner (faster), Red = loser (slower)
if (isHighlighted && memPackResult != null && acBinaryResult != null)
{
- var isMemPack = (result.Engine == EngineMemoryPack && result.IoMode == IoByteArray);
+ var isMemPack = (result.Engine == Configuration.EngineMemoryPack && result.IoMode == Configuration.IoByteArray);
var memPackFaster = RtPerOp(memPackResult) < RtPerOp(acBinaryResult);
if (isMemPack)
@@ -3103,13 +2926,13 @@ public static class Program
// Overall AcBinary (SGen) vs MemoryPack comparison (baseline switched MessagePack → MemoryPack as SOTA reference).
// AcBinary side is restricted to DispatchMode == SGen — apples-to-apples vs MemoryPack which is also source-generated.
// The Runtime variant is shown side-by-side in each per-test fancy table for SGen-speedup context, but excluded from this headline.
- var memPackSerResults = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray) && r.SerializeTimeMs > 0).ToList();
- var memPackDesResults = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray) && r.DeserializeTimeMs > 0).ToList();
- var memPackRtResults = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray) && r.RoundTripTimeMs > 0).ToList();
+ var memPackSerResults = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray) && r.SerializeTimeMs > 0).ToList();
+ var memPackDesResults = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray) && r.DeserializeTimeMs > 0).ToList();
+ var memPackRtResults = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray) && r.RoundTripTimeMs > 0).ToList();
- var acBinarySerResults = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen) && r.SerializeTimeMs > 0).ToList();
- var acBinaryDesResults = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen) && r.DeserializeTimeMs > 0).ToList();
- var acBinaryRtResults = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen) && r.RoundTripTimeMs > 0).ToList();
+ var acBinarySerResults = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen) && r.SerializeTimeMs > 0).ToList();
+ var acBinaryDesResults = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen) && r.DeserializeTimeMs > 0).ToList();
+ var acBinaryRtResults = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen) && r.RoundTripTimeMs > 0).ToList();
// Skip comparison if no data available
if (memPackRtResults.Count == 0 || acBinaryRtResults.Count == 0)
@@ -3128,8 +2951,8 @@ public static class Program
// - Median of per-cell ratios — outlier-resistant.
// The geo/median variants surface when a single cell dominates the arithmetic average
// (typical when one cell's µs-per-op is an order of magnitude larger than the others).
- var sizeAcResults = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen)).ToList();
- var sizeMpResults = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray)).ToList();
+ var sizeAcResults = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen)).ToList();
+ var sizeMpResults = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray)).ToList();
var serStats = ComputeOverallStats(acBinarySerResults, memPackSerResults, SerPerOp);
var desStats = ComputeOverallStats(acBinaryDesResults, memPackDesResults, DesPerOp);
@@ -3183,12 +3006,12 @@ public static class Program
private static void SaveResults(List results, List testDataSets)
{
- Directory.CreateDirectory(ResultsDirectory);
+ Directory.CreateDirectory(Configuration.ResultsDirectory);
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
- var baseFileName = $"Console.FullBenchmark_{BuildConfiguration}_{timestamp}";
- var logFilePath = Path.Combine(ResultsDirectory, $"{baseFileName}.log");
- var outputFilePath = Path.Combine(ResultsDirectory, $"{baseFileName}.output");
+ var baseFileName = $"Console.FullBenchmark_{Configuration.BuildConfiguration}_{timestamp}";
+ var logFilePath = Path.Combine(Configuration.ResultsDirectory, $"{baseFileName}.log");
+ var outputFilePath = Path.Combine(Configuration.ResultsDirectory, $"{baseFileName}.output");
// Save binary output to separate .output file
var largeTestData = testDataSets.FirstOrDefault(t => t.Name.StartsWith("Large"));
@@ -3208,7 +3031,7 @@ public static class Program
outputSb.AppendLine("Hex dump:");
outputSb.AppendLine(FormatHexDump(serializedBytes));
- File.WriteAllText(outputFilePath, outputSb.ToString(), Utf8NoBom);
+ File.WriteAllText(outputFilePath, outputSb.ToString(), Configuration.Utf8NoBom);
System.Console.WriteLine($"✓ Binary output saved to: {outputFilePath}");
}
@@ -3217,10 +3040,10 @@ public static class Program
sb.AppendLine("╔══════════════════════════════════════════════════════════════════════════════════════════════════════╗");
sb.AppendLine("║ SERIALIZER BENCHMARK RESULTS ║");
sb.AppendLine($"║ Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}".PadRight(100) + "║");
- sb.AppendLine($"║ Build: {BuildConfiguration}".PadRight(100) + "║");
+ sb.AppendLine($"║ Build: {Configuration.BuildConfiguration}".PadRight(100) + "║");
sb.AppendLine($"║ Charset: {GetCurrentCharsetName()}".PadRight(100) + "║");
- sb.AppendLine($"║ Iterations: per-cell adaptive (~{TargetSampleMs} ms target)".PadRight(100) + "║");
- sb.AppendLine($"║ Samples: {BenchmarkSamples} (median) + 1 pilot discarded".PadRight(100) + "║");
+ sb.AppendLine($"║ Iterations: per-cell adaptive (~{Configuration.TargetSampleMs} ms target)".PadRight(100) + "║");
+ sb.AppendLine($"║ Samples: {Configuration.BenchmarkSamples} (median) + 1 pilot discarded".PadRight(100) + "║");
sb.AppendLine($"║ Test Type: {testDataSets.FirstOrDefault()?.TypeName ?? "unknown"}".PadRight(100) + "║");
sb.AppendLine("╚══════════════════════════════════════════════════════════════════════════════════════════════════════╝");
sb.AppendLine();
@@ -3262,10 +3085,10 @@ public static class Program
{
// Order by per-op µs (iter-independent) — rows may have different iter counts post-calibration.
var testResults = results.Where(r => r.TestDataName == testData.DisplayName).OrderBy(r => RtPerOp(r)).ToList();
- var memPackResult = testResults.FirstOrDefault(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray));
+ var memPackResult = testResults.FirstOrDefault(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray));
// Pin the comparison to AcBinary's SGen variant — apples-to-apples vs MemoryPack (also source-generated).
// The Runtime variant is shown alongside in the table for context, not used as the headline number.
- var acBinaryResult = testResults.FirstOrDefault(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen));
+ var acBinaryResult = testResults.FirstOrDefault(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen));
sb.AppendLine();
sb.AppendLine($"--- {testData.DisplayName} ---");
@@ -3275,7 +3098,7 @@ public static class Program
var rank = 1;
foreach (var result in testResults)
{
- var isHighlighted = ((result.Engine == EngineMemoryPack || result.Engine == EngineAcBinary) && result.IoMode == IoByteArray);
+ var isHighlighted = ((result.Engine == Configuration.EngineMemoryPack || result.Engine == Configuration.EngineAcBinary) && result.IoMode == Configuration.IoByteArray);
var prefix = isHighlighted ? "► " : " ";
var size = $"{result.SerializedSize:N0}";
@@ -3312,13 +3135,13 @@ public static class Program
sb.AppendLine();
sb.AppendLine("=== AcBinary (Byte[], SGen) vs MemoryPack (Byte[]) (Overall) ===");
- var memPackSerResults2 = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray) && r.SerializeTimeMs > 0).ToList();
- var memPackDesResults2 = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray) && r.DeserializeTimeMs > 0).ToList();
- var memPackRtResults2 = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray) && r.RoundTripTimeMs > 0).ToList();
+ var memPackSerResults2 = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray) && r.SerializeTimeMs > 0).ToList();
+ var memPackDesResults2 = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray) && r.DeserializeTimeMs > 0).ToList();
+ var memPackRtResults2 = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray) && r.RoundTripTimeMs > 0).ToList();
- var acBinarySerResults2 = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen) && r.SerializeTimeMs > 0).ToList();
- var acBinaryDesResults2 = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen) && r.DeserializeTimeMs > 0).ToList();
- var acBinaryRtResults2 = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen) && r.RoundTripTimeMs > 0).ToList();
+ var acBinarySerResults2 = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen) && r.SerializeTimeMs > 0).ToList();
+ var acBinaryDesResults2 = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen) && r.DeserializeTimeMs > 0).ToList();
+ var acBinaryRtResults2 = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen) && r.RoundTripTimeMs > 0).ToList();
// Skip comparison block if either side has no Byte[] data — happens in AsyncPipe-only mode
// where only NamedPipe rows exist (no MemoryPack baseline, no AcBinary Byte[] reference).
@@ -3326,18 +3149,18 @@ public static class Program
if (memPackRtResults2.Count == 0 || acBinaryRtResults2.Count == 0)
{
sb.AppendLine(" (Comparison requires both serialize and deserialize data)");
- File.WriteAllText(logFilePath, sb.ToString(), Utf8NoBom);
+ File.WriteAllText(logFilePath, sb.ToString(), Configuration.Utf8NoBom);
System.Console.WriteLine($"✓ Results saved to: {logFilePath}");
- var llmFilePathEarly = Path.Combine(ResultsDirectory, $"{baseFileName}.LLM");
+ var llmFilePathEarly = Path.Combine(Configuration.ResultsDirectory, $"{baseFileName}.LLM");
SaveLlmResults(llmFilePathEarly, results, testDataSets);
return;
}
// Per-cell-paired aggregation: arithmetic / geometric / median. See PrintSummary's parallel
// block + the OverallStats record for the rationale (per-cell ratio vs magnitude-weighted mean).
- var sizeAcResults2 = results.Where(r => (r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen)).ToList();
- var sizeMpResults2 = results.Where(r => (r.Engine == EngineMemoryPack && r.IoMode == IoByteArray)).ToList();
+ var sizeAcResults2 = results.Where(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen)).ToList();
+ var sizeMpResults2 = results.Where(r => (r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray)).ToList();
AppendOverallLine(sb, "Serialize", "µs/op", ComputeOverallStats(acBinarySerResults2, memPackSerResults2, SerPerOp));
AppendOverallLine(sb, "Ser Alloc", "B/op", ComputeOverallStats(acBinarySerResults2, memPackSerResults2, r => r.SerializeAllocBytesPerOp), "F0");
@@ -3346,11 +3169,11 @@ public static class Program
AppendOverallLine(sb, "Round-trip", "µs/op", ComputeOverallStats(acBinaryRtResults2, memPackRtResults2, RtPerOp));
AppendOverallLine(sb, "Size", "B", ComputeOverallStats(sizeAcResults2, sizeMpResults2, r => r.SerializedSize), "F0");
- File.WriteAllText(logFilePath, sb.ToString(), Utf8NoBom);
+ File.WriteAllText(logFilePath, sb.ToString(), Configuration.Utf8NoBom);
System.Console.WriteLine($"✓ Results saved to: {logFilePath}");
// Save LLM-optimized results
- var llmFilePath = Path.Combine(ResultsDirectory, $"{baseFileName}.LLM");
+ var llmFilePath = Path.Combine(Configuration.ResultsDirectory, $"{baseFileName}.LLM");
SaveLlmResults(llmFilePath, results, testDataSets);
}
@@ -3358,8 +3181,8 @@ public static class Program
{
var sb = new StringBuilder();
var testTypeName = testDataSets.FirstOrDefault()?.TypeName ?? "unknown";
- sb.AppendLine($"# AcBinary Benchmark {BuildConfiguration} {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
- sb.AppendLine($"Charset: {GetCurrentCharsetName()} | Iterations: per-cell adaptive (target ~{TargetSampleMs} ms/sample) | Warmup: {WarmupIterations} per phase (Ser/Des isolated) | Samples: {BenchmarkSamples} (median) + 1 pilot discarded | .NET: {Environment.Version} | TestType: {testTypeName} | UnstableCV threshold: {UnstableCVThreshold * 100:F0}%");
+ sb.AppendLine($"# AcBinary Benchmark {Configuration.BuildConfiguration} {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
+ sb.AppendLine($"Charset: {GetCurrentCharsetName()} | Iterations: per-cell adaptive (target ~{Configuration.TargetSampleMs} ms/sample) | Warmup: {Configuration.WarmupIterations} per phase (Ser/Des isolated) | Samples: {Configuration.BenchmarkSamples} (median) + 1 pilot discarded | .NET: {Environment.Version} | TestType: {testTypeName} | UnstableCV threshold: {Configuration.UnstableCVThreshold * 100:F0}%");
sb.AppendLine("Baseline: MemoryPack (Byte[]) (SOTA reference) | Verified: round-trip correctness checked once per cell before warmup");
// Options summary
@@ -3379,7 +3202,7 @@ public static class Program
// Flat results table sorted by test data then round-trip (now includes Alloc + Iter columns).
// Iter column shows per-row Ser/Des iteration counts (post-adaptive-calibration), so the reader
- // can verify that each cell's batch sample landed near the TargetSampleMs window.
+ // can verify that each cell's batch sample landed near the Configuration.TargetSampleMs window.
sb.AppendLine();
sb.AppendLine("## Results");
sb.AppendLine();
@@ -3429,8 +3252,8 @@ public static class Program
// arith mean is magnitude-weighted (Large cell dominates); geo/median are per-cell-equal
// signals. Adding this lets an LLM diagnose whether a headline delta is a real overall
// win/loss or a single-cell artifact.
- var memPackByteArrayResults = results.Where(r => r.Engine == EngineMemoryPack && r.IoMode == IoByteArray).ToList();
- var acBinarySGenByteArrayResults = results.Where(r => r.Engine == EngineAcBinary && r.IoMode == IoByteArray && r.DispatchMode == ModeSGen).ToList();
+ var memPackByteArrayResults = results.Where(r => r.Engine == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray).ToList();
+ var acBinarySGenByteArrayResults = results.Where(r => r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen).ToList();
var memPackSerResultsLlm = memPackByteArrayResults.Where(r => r.SerializeTimeMs > 0).ToList();
var memPackDesResultsLlm = memPackByteArrayResults.Where(r => r.DeserializeTimeMs > 0).ToList();
var memPackRtResultsLlm = memPackByteArrayResults.Where(r => r.RoundTripTimeMs > 0).ToList();
@@ -3455,7 +3278,7 @@ public static class Program
sb.AppendLine("```");
}
- File.WriteAllText(filePath, sb.ToString(), Utf8NoBom);
+ File.WriteAllText(filePath, sb.ToString(), Configuration.Utf8NoBom);
System.Console.WriteLine($"✓ LLM results saved to: {filePath}");
}
diff --git a/AyCode.Core.Serializers.Console/README.md b/AyCode.Core.Serializers.Console/README.md
index 1827386..4d58e7d 100644
--- a/AyCode.Core.Serializers.Console/README.md
+++ b/AyCode.Core.Serializers.Console/README.md
@@ -12,7 +12,7 @@ Standalone benchmark console application for comparing serializer performance. T
## Key Files
-- **`Program.cs`** — Benchmark runner. Modes: `all` (default), `quick` (fewer iterations), `serialize`, `deserialize`, `profiler` (memory profiler warmup). Outputs results to `Test_Benchmark_Results/Benchmark/`. Iterations: 5000 warmup + 1000 test (Release), 0+1 (Debug).
+- **`Program.cs`** — Benchmark runner. Modes: `all` (default), `quick` (fewer iterations), `serialize`, `deserialize`. Outputs results to `Test_Benchmark_Results/Benchmark/`. Iterations: 5000 warmup + 1000 test (Release), 0+1 (Debug).
- **`BenchmarkTestDataProvider.cs`** — Test data factory producing 5 data shapes:
- Small (2x2x2x2), Medium (3x3x3x4), Large (5x5x5x10)
- Repeated Strings (10 items, string deduplication testing)
diff --git a/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs b/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs
index ce1b20c..1a829d5 100644
--- a/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs
+++ b/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs
@@ -78,20 +78,6 @@ public static class BenchmarkTestDataProvider
};
}
- public static TestOrder CreateProfilerOrder()
- {
- TestDataFactory.ResetIdCounter();
- var sharedTag = TestDataFactory.CreateTag("SharedTag");
- var sharedUser = TestDataFactory.CreateUser("shareduser");
- return TestDataFactory.CreateOrder(
- itemCount: 3,
- palletsPerItem: 3,
- measurementsPerPallet: 3,
- pointsPerMeasurement: 4,
- sharedTag: sharedTag,
- sharedUser: sharedUser);
- }
-
private static TestDataSet CreateSmallTestData(bool resetId = true)
{
if (resetId) TestDataFactory.ResetIdCounter();