diff --git a/AyCode.Core.Serializers.Console/BenchmarkLoop.cs b/AyCode.Core.Serializers.Console/BenchmarkLoop.cs
index 13fc83b..7ce5ee8 100644
--- a/AyCode.Core.Serializers.Console/BenchmarkLoop.cs
+++ b/AyCode.Core.Serializers.Console/BenchmarkLoop.cs
@@ -227,7 +227,7 @@ internal static class BenchmarkLoop
result.RoundTripIterations = rtIter;
// Process-wide allocation measurement: server-drain-thread allocations (server-side new byte[len])
// also show up — otherwise current-thread alloc would only count the client side and look ~halved.
- result.RoundTripAllocBytesPerOp = MeasureAllocationTotal(() => serializer.Serialize(), rtIter, $"{groupLabel} [RT alloc]");
+ result.RoundTripAllocBytesPerOp = MeasureAllocation(() => serializer.Serialize(), rtIter, $"{groupLabel} [RT alloc]", processWide: true);
}
// mode == "deserialize" alone is meaningless for a round-trip-only benchmark; skip silently.
}
@@ -611,43 +611,24 @@ internal static class BenchmarkLoop
}
///
- /// Measures per-call allocation in bytes after a clean GC. Single dedicated sample (no median) — keeps timing samples pure.
+ /// Measures per-call allocation in bytes after a clean GC. Single dedicated sample (no median) — keeps
+ /// timing samples pure. When is true, uses
+ /// instead of
+ /// — needed for round-trip-only benchmarks (NamedPipe etc.) where the work happens across multiple
+ /// threads (server-side new byte[len] buffers, drain-pump-thread allocations). Per-thread mode
+ /// is slightly cleaner for in-memory benchmarks; process-wide mode is slightly noisier (background
+ /// threads / GC bookkeeping leak in) but over 1000 iterations the signal dominates.
///
- internal static long MeasureAllocation(Action action, int iterations, string? progressLabel = null)
+ internal static long MeasureAllocation(Action action, int iterations, string? progressLabel = null, bool processWide = false)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var sw = Stopwatch.StartNew();
- var before = GC.GetAllocatedBytesForCurrentThread();
+ var before = processWide ? GC.GetTotalAllocatedBytes(precise: true) : GC.GetAllocatedBytesForCurrentThread();
RunWithProgress(action, iterations, progressLabel, samples: 1, sampleIndex: 0);
-
- var after = GC.GetAllocatedBytesForCurrentThread();
- sw.Stop();
- EndProgress(progressLabel, sw.Elapsed.TotalMilliseconds);
- return (after - before) / iterations;
- }
-
- ///
- /// Process-wide allocation measurement — needed for round-trip-only benchmarks (NamedPipe etc.) where
- /// the work happens across multiple threads. would
- /// only count the caller-thread allocations, missing the server-side new byte[len] buffers and
- /// any drain-pump-thread allocations. covers the entire process.
- /// Slightly noisier than the per-thread variant (background threads / GC bookkeeping leak in), but
- /// over 1000 iterations the signal dominates.
- ///
- internal static long MeasureAllocationTotal(Action action, int iterations, string? progressLabel = null)
- {
- GC.Collect();
- GC.WaitForPendingFinalizers();
- GC.Collect();
-
- var sw = Stopwatch.StartNew();
- var before = GC.GetTotalAllocatedBytes(precise: true);
- RunWithProgress(action, iterations, progressLabel, samples: 1, sampleIndex: 0);
-
- var after = GC.GetTotalAllocatedBytes(precise: true);
+ var after = processWide ? GC.GetTotalAllocatedBytes(precise: true) : GC.GetAllocatedBytesForCurrentThread();
sw.Stop();
EndProgress(progressLabel, sw.Elapsed.TotalMilliseconds);
return (after - before) / iterations;
diff --git a/AyCode.Core.Serializers.Console/BenchmarkResult.cs b/AyCode.Core.Serializers.Console/BenchmarkResult.cs
index 7f80c20..6b60dbd 100644
--- a/AyCode.Core.Serializers.Console/BenchmarkResult.cs
+++ b/AyCode.Core.Serializers.Console/BenchmarkResult.cs
@@ -1,3 +1,5 @@
+using AyCode.Core.Serializers.Console.Benchmarks;
+
namespace AyCode.Core.Serializers.Console;
///
@@ -8,9 +10,9 @@ namespace AyCode.Core.Serializers.Console;
internal sealed class BenchmarkResult
{
public string TestDataName { get; set; } = "";
- public string Engine { get; set; } = "";
- public string IoMode { get; set; } = "";
- public string DispatchMode { get; set; } = "";
+ public BenchmarkEngine Engine { get; set; }
+ public BenchmarkIoMode IoMode { get; set; }
+ public BenchmarkDispatchMode DispatchMode { get; set; }
public string OptionsPreset { get; set; } = "";
/// True if Serialize() captures a full round-trip and Deserialize() is a no-op
@@ -20,7 +22,7 @@ internal sealed class BenchmarkResult
public bool IsRoundTripOnly { get; set; }
/// Synthesized display name for backwards compatibility / single-string-row scenarios. Includes DispatchMode so SGen and Runtime variants of the same preset don't collide in grouping (e.g. SUMMARY: WINNERS).
- public string SerializerName => $"{Engine} ({IoMode}, {OptionsPreset}, {DispatchMode})";
+ public string SerializerName => $"{Engine.ToDisplay()} ({IoMode.ToDisplay()}, {OptionsPreset}, {DispatchMode.ToDisplay()})";
public string? OptionsDescription { get; set; }
public int SerializedSize { get; set; }
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBenchmark.cs
index e77a187..1bd4883 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBenchmark.cs
@@ -14,9 +14,9 @@ internal sealed class AcBinaryBenchmark : ISerializerBenchmark
private readonly AcBinarySerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => Configuration.EngineAcBinary;
- public string IoMode => Configuration.IoByteArray;
- public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
+ public BenchmarkEngine Engine => BenchmarkEngine.AcBinary;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.ByteArray;
+ public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBufferWriterBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBufferWriterBenchmark.cs
index aacafe1..58415b2 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBufferWriterBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryBufferWriterBenchmark.cs
@@ -16,9 +16,9 @@ internal sealed class AcBinaryBufferWriterBenchmark : ISerializerBenchmark
private readonly byte[] _serialized;
private readonly ArrayBufferWriter _bufferWriter;
- public string Engine => Configuration.EngineAcBinary;
- public string IoMode => Configuration.IoBufWrReuse;
- public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
+ public BenchmarkEngine Engine => BenchmarkEngine.AcBinary;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.BufWrReuse;
+ public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryFreshBufferWriterBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryFreshBufferWriterBenchmark.cs
index 2e43f42..420ceb9 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryFreshBufferWriterBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryFreshBufferWriterBenchmark.cs
@@ -18,9 +18,9 @@ internal sealed class AcBinaryFreshBufferWriterBenchmark : ISerializerBenchmark
private readonly AcBinarySerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => Configuration.EngineAcBinary;
- public string IoMode => Configuration.IoBufWrNew;
- public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
+ public BenchmarkEngine Engine => BenchmarkEngine.AcBinary;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.BufWrNew;
+ public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryPipeBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryPipeBenchmark.cs
index 1b09377..363e7cb 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryPipeBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryPipeBenchmark.cs
@@ -47,9 +47,9 @@ internal sealed class AcBinaryInMemoryPipeBenchmark : ISerializerBenchmark, IDis
private bool _captureResult;
private bool _disposed;
- public string Engine => Configuration.EngineAcBinary;
- public string IoMode => Configuration.IoInMemoryPipe;
- public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
+ public BenchmarkEngine Engine => BenchmarkEngine.AcBinary;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.InMemoryPipe;
+ public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryRawByteArrayBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryRawByteArrayBenchmark.cs
index ccde326..d2e305a 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryRawByteArrayBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryInMemoryRawByteArrayBenchmark.cs
@@ -38,9 +38,9 @@ internal sealed class AcBinaryInMemoryRawByteArrayBenchmark : ISerializerBenchma
private bool _captureResult;
private bool _disposed;
- public string Engine => Configuration.EngineAcBinary;
- public string IoMode => Configuration.IoInMemoryRaw;
- public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
+ public BenchmarkEngine Engine => BenchmarkEngine.AcBinary;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.InMemoryRaw;
+ public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeBenchmark.cs
index 9065cfc..99bd6c7 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeBenchmark.cs
@@ -62,9 +62,9 @@ internal sealed class AcBinaryNamedPipeBenchmark : ISerializerBenchmark, IDispos
private bool _captureResult; // toggle: when true, ConsumeLoop stores result; otherwise discards
private bool _disposed;
- public string Engine => Configuration.EngineAcBinary;
- public string IoMode => Configuration.IoNamedPipe;
- public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
+ public BenchmarkEngine Engine => BenchmarkEngine.AcBinary;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.NamedPipe;
+ public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeRawByteArrayBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeRawByteArrayBenchmark.cs
index d6b49ec..6f85d79 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeRawByteArrayBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/AcBinaryNamedPipeRawByteArrayBenchmark.cs
@@ -49,9 +49,9 @@ internal sealed class AcBinaryNamedPipeRawByteArrayBenchmark : ISerializerBenchm
private bool _captureResult; // toggle: when true, ConsumerLoop stores result; otherwise discards
private bool _disposed;
- public string Engine => Configuration.EngineAcBinary;
- public string IoMode => Configuration.IoNamedPipeRaw;
- public string DispatchMode => _options.UseGeneratedCode ? Configuration.ModeSGen : Configuration.ModeRuntime;
+ public BenchmarkEngine Engine => BenchmarkEngine.AcBinary;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.NamedPipeRaw;
+ public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime;
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/BenchmarkEnums.cs b/AyCode.Core.Serializers.Console/Benchmarks/BenchmarkEnums.cs
new file mode 100644
index 0000000..3e4fc04
--- /dev/null
+++ b/AyCode.Core.Serializers.Console/Benchmarks/BenchmarkEnums.cs
@@ -0,0 +1,90 @@
+namespace AyCode.Core.Serializers.Console.Benchmarks;
+
+///
+/// Serializer engine identifier — replaces the prior Configuration.EngineXxx string constants
+/// with a type-safe enum. The benchmark-result Engine column uses for
+/// the human-readable form.
+///
+internal enum BenchmarkEngine
+{
+ AcBinary,
+ MemoryPack,
+#if !AYCODE_NATIVEAOT
+ MessagePack,
+#endif
+ SystemTextJson,
+}
+
+///
+/// I/O mode identifier — replaces the prior Configuration.IoXxx string constants. Note that
+/// and share the display string "NamedPipe"
+/// (they distinguish chunked-framed vs raw-byte[] semantics, but render identically in the IO column);
+/// the same applies to + ("Pipe(in-mem)").
+///
+internal enum BenchmarkIoMode
+{
+ ByteArray,
+ BufWrReuse,
+ BufWrNew,
+ String,
+ NamedPipe,
+ NamedPipeRaw,
+ InMemoryPipe,
+ InMemoryRaw,
+}
+
+///
+/// Dispatch mode identifier — replaces the prior Configuration.ModeXxx string constants.
+/// Describes how property access / type dispatch happens for a given benchmark row:
+///
+/// - — compile-time source generator path (Unsafe.As<T> direct fields, slot-array wrapper lookup).
+/// - — reflection / compiled-delegate path.
+/// - — SGen root with non-SGen child types reached via bridge methods (see docs/BINARY/BINARY_SGEN.md).
+///
+///
+internal enum BenchmarkDispatchMode
+{
+ SGen,
+ Runtime,
+ Hybrid,
+}
+
+///
+/// Display-string converters for the benchmark enums. Renders enum values into the column-friendly
+/// human-readable form used by the per-row console table, the .log file CSV/formatted output,
+/// and the .LLM markdown table. Centralised here so every output formatter renders identically.
+///
+internal static class BenchmarkEnumExtensions
+{
+ internal static string ToDisplay(this BenchmarkEngine engine) => engine switch
+ {
+ BenchmarkEngine.AcBinary => "AcBinary",
+ BenchmarkEngine.MemoryPack => "MemoryPack",
+#if !AYCODE_NATIVEAOT
+ BenchmarkEngine.MessagePack => "MessagePack",
+#endif
+ BenchmarkEngine.SystemTextJson => "System.Text.Json",
+ _ => throw new ArgumentOutOfRangeException(nameof(engine), engine, null),
+ };
+
+ internal static string ToDisplay(this BenchmarkIoMode mode) => mode switch
+ {
+ BenchmarkIoMode.ByteArray => "Byte[]",
+ BenchmarkIoMode.BufWrReuse => "BufWr reuse",
+ BenchmarkIoMode.BufWrNew => "BufWr new",
+ BenchmarkIoMode.String => "String",
+ BenchmarkIoMode.NamedPipe => "NamedPipe",
+ BenchmarkIoMode.NamedPipeRaw => "NamedPipe",
+ BenchmarkIoMode.InMemoryPipe => "Pipe(in-mem)",
+ BenchmarkIoMode.InMemoryRaw => "Pipe(in-mem)",
+ _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
+ };
+
+ internal static string ToDisplay(this BenchmarkDispatchMode mode) => mode switch
+ {
+ BenchmarkDispatchMode.SGen => "SGen",
+ BenchmarkDispatchMode.Runtime => "Runtime",
+ BenchmarkDispatchMode.Hybrid => "Hybrid",
+ _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
+ };
+}
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/ISerializerBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/ISerializerBenchmark.cs
index ed39f22..2215e24 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/ISerializerBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/ISerializerBenchmark.cs
@@ -13,16 +13,16 @@ namespace AyCode.Core.Serializers.Console.Benchmarks;
///
internal interface ISerializerBenchmark
{
- /// Serializer engine — e.g. "AcBinary", "MemoryPack", "MessagePack".
- string Engine { get; }
- /// I/O mode — e.g. "Byte[]", "BufWr reuse", "BufWr new", "NamedPipe", "FileStream".
- string IoMode { get; }
- /// Dispatch mode — "SGen", "Runtime", or "Hybrid". For AcBinary derived from UseGeneratedCode + child-type SGen coverage; non-AcBinary engines report their own native dispatch model.
- string DispatchMode { get; }
- /// Options preset name — e.g. "FastMode", "Default", "NoIntern", "WithCompression".
+ /// Serializer engine — typed enum, see for the human-readable form.
+ BenchmarkEngine Engine { get; }
+ /// I/O mode — typed enum, see for the human-readable form.
+ BenchmarkIoMode IoMode { get; }
+ /// Dispatch mode — typed enum ( / / ). For AcBinary derived from UseGeneratedCode + child-type SGen coverage; non-AcBinary engines report their own native dispatch model.
+ BenchmarkDispatchMode DispatchMode { get; }
+ /// Options preset name — e.g. "FastMode", "Default", "NoIntern", "WithCompression". Stays string because preset names are open-ended (per-instance constructor argument).
string OptionsPreset { get; }
/// Synthesized display name from Engine + IoMode + OptionsPreset.
- string Name => $"{Engine} ({IoMode}, {OptionsPreset})";
+ string Name => $"{Engine.ToDisplay()} ({IoMode.ToDisplay()}, {OptionsPreset})";
int SerializedSize { get; }
string? OptionsDescription => null;
/// One-time SERIALIZER-side setup allocation cost (e.g., pre-allocated ArrayBufferWriter with internal buffer). Captured in constructor; 0 for byte[] API and Fresh-BufWriter variants.
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBenchmark.cs
index 52444e3..1606ed5 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBenchmark.cs
@@ -15,9 +15,9 @@ internal sealed class MemoryPackBenchmark : ISerializerBenchmark
private readonly MemoryPackSerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => Configuration.EngineMemoryPack;
- public string IoMode => Configuration.IoByteArray;
- public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
+ public BenchmarkEngine Engine => BenchmarkEngine.MemoryPack;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.ByteArray;
+ public BenchmarkDispatchMode DispatchMode => BenchmarkDispatchMode.SGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBufferWriterBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBufferWriterBenchmark.cs
index 0320e6f..e61aa7f 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBufferWriterBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackBufferWriterBenchmark.cs
@@ -17,9 +17,9 @@ internal sealed class MemoryPackBufferWriterBenchmark : ISerializerBenchmark
private readonly byte[] _serialized;
private readonly ArrayBufferWriter _bufferWriter;
- public string Engine => Configuration.EngineMemoryPack;
- public string IoMode => Configuration.IoBufWrReuse;
- public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
+ public BenchmarkEngine Engine => BenchmarkEngine.MemoryPack;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.BufWrReuse;
+ public BenchmarkDispatchMode DispatchMode => BenchmarkDispatchMode.SGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes { get; }
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackFreshBufferWriterBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackFreshBufferWriterBenchmark.cs
index 032f21a..e69cde4 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackFreshBufferWriterBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/MemoryPackFreshBufferWriterBenchmark.cs
@@ -15,9 +15,9 @@ internal sealed class MemoryPackFreshBufferWriterBenchmark : ISerializerBenchmar
private readonly MemoryPackSerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => Configuration.EngineMemoryPack;
- public string IoMode => Configuration.IoBufWrNew;
- public string DispatchMode => Configuration.ModeSGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
+ public BenchmarkEngine Engine => BenchmarkEngine.MemoryPack;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.BufWrNew;
+ public BenchmarkDispatchMode DispatchMode => BenchmarkDispatchMode.SGen; // MemoryPack always uses [MemoryPackable] source-generated formatters
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/MessagePackBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/MessagePackBenchmark.cs
index 7f4e2b0..af2d67b 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/MessagePackBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/MessagePackBenchmark.cs
@@ -18,9 +18,9 @@ internal sealed class MessagePackBenchmark : ISerializerBenchmark
private readonly MessagePackSerializerOptions _options;
private readonly byte[] _serialized;
- public string Engine => Configuration.EngineMessagePack;
- public string IoMode => Configuration.IoByteArray;
- public string DispatchMode => Configuration.ModeSGen; // MessagePack uses [MessagePackObject] source-generated formatters (StandardResolver)
+ public BenchmarkEngine Engine => BenchmarkEngine.MessagePack;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.ByteArray;
+ public BenchmarkDispatchMode DispatchMode => BenchmarkDispatchMode.SGen; // MessagePack uses [MessagePackObject] source-generated formatters (StandardResolver)
public string OptionsPreset { get; }
public int SerializedSize => _serialized.Length;
public long SetupSerializeAllocBytes => 0;
diff --git a/AyCode.Core.Serializers.Console/Benchmarks/SystemTextJsonBenchmark.cs b/AyCode.Core.Serializers.Console/Benchmarks/SystemTextJsonBenchmark.cs
index 2acca54..2d45ccc 100644
--- a/AyCode.Core.Serializers.Console/Benchmarks/SystemTextJsonBenchmark.cs
+++ b/AyCode.Core.Serializers.Console/Benchmarks/SystemTextJsonBenchmark.cs
@@ -17,9 +17,9 @@ internal sealed class SystemTextJsonBenchmark : ISerializerBenchmark
private readonly string _serialized;
private readonly byte[] _serializedUtf8;
- 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 BenchmarkEngine Engine => BenchmarkEngine.SystemTextJson;
+ public BenchmarkIoMode IoMode => BenchmarkIoMode.String;
+ public BenchmarkDispatchMode DispatchMode => BenchmarkDispatchMode.Runtime; // 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;
diff --git a/AyCode.Core.Serializers.Console/Configuration.cs b/AyCode.Core.Serializers.Console/Configuration.cs
index d7290de..1e52e64 100644
--- a/AyCode.Core.Serializers.Console/Configuration.cs
+++ b/AyCode.Core.Serializers.Console/Configuration.cs
@@ -41,24 +41,7 @@ internal static class Configuration
// 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)";
+ // 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.
@@ -67,14 +50,6 @@ internal static class Configuration
// 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
diff --git a/AyCode.Core.Serializers.Console/Output.cs b/AyCode.Core.Serializers.Console/Output.cs
index e6247d5..1cccfcc 100644
--- a/AyCode.Core.Serializers.Console/Output.cs
+++ b/AyCode.Core.Serializers.Console/Output.cs
@@ -1,4 +1,5 @@
using AyCode.Core.Serializers.Binaries;
+using AyCode.Core.Serializers.Console.Benchmarks;
using AyCode.Core.Tests.TestModels;
using System.Globalization;
using System.Runtime.CompilerServices;
@@ -191,10 +192,10 @@ internal static class Output
// 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 == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray));
+ var memPackResult = testResults.FirstOrDefault(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray));
// 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 == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen));
+ var acBinaryResult = testResults.FirstOrDefault(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen));
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).
@@ -215,8 +216,8 @@ internal static class Output
// 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 == Configuration.EngineMemoryPack && result.IoMode == Configuration.IoByteArray)
- || (result.Engine == Configuration.EngineAcBinary && result.IoMode == Configuration.IoByteArray && result.DispatchMode == Configuration.ModeSGen);
+ var isHighlighted = (result.Engine == BenchmarkEngine.MemoryPack && result.IoMode == BenchmarkIoMode.ByteArray)
+ || (result.Engine == BenchmarkEngine.AcBinary && result.IoMode == BenchmarkIoMode.ByteArray && result.DispatchMode == BenchmarkDispatchMode.SGen);
var prefix = isHighlighted ? "│►" : "│ ";
var suffix = isHighlighted ? "◄│" : " │";
@@ -224,7 +225,7 @@ internal static class Output
// Color logic: Green = winner (faster), Red = loser (slower)
if (isHighlighted && memPackResult != null && acBinaryResult != null)
{
- var isMemPack = (result.Engine == Configuration.EngineMemoryPack && result.IoMode == Configuration.IoByteArray);
+ var isMemPack = (result.Engine == BenchmarkEngine.MemoryPack && result.IoMode == BenchmarkIoMode.ByteArray);
var memPackFaster = RtPerOp(memPackResult) < RtPerOp(acBinaryResult);
if (isMemPack)
@@ -237,7 +238,7 @@ internal static class Output
}
}
- System.Console.WriteLine($"{prefix}{rank++,4} │ {result.Engine,-11} │ {result.OptionsPreset,-22} │ {result.IoMode,-12} │ {result.DispatchMode,-8} │ {setup,14} │ {size,8} │ {ser,10} │ {serAlloc,10} │ {des,10} │ {desAlloc,10} │ {rt,10} │ {rtAlloc,10}{suffix}");
+ System.Console.WriteLine($"{prefix}{rank++,4} │ {result.Engine.ToDisplay(),-11} │ {result.OptionsPreset,-22} │ {result.IoMode.ToDisplay(),-12} │ {result.DispatchMode.ToDisplay(),-8} │ {setup,14} │ {size,8} │ {ser,10} │ {serAlloc,10} │ {des,10} │ {desAlloc,10} │ {rt,10} │ {rtAlloc,10}{suffix}");
if (isHighlighted)
{
@@ -366,13 +367,13 @@ internal static class Output
System.Console.WriteLine($"{"Fastest Round-trip",-20} │ {fastestRt.Name,-40} │ {fastestRt.AvgPerOp,12:F2} µs/op");
// Overall AcBinary (SGen) vs MemoryPack comparison.
- 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 memPackSerResults = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray) && r.SerializeTimeMs > 0).ToList();
+ var memPackDesResults = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray) && r.DeserializeTimeMs > 0).ToList();
+ var memPackRtResults = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray) && 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();
+ var acBinarySerResults = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen) && r.SerializeTimeMs > 0).ToList();
+ var acBinaryDesResults = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen) && r.DeserializeTimeMs > 0).ToList();
+ var acBinaryRtResults = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen) && r.RoundTripTimeMs > 0).ToList();
// Skip comparison if no data available
if (memPackRtResults.Count == 0 || acBinaryRtResults.Count == 0)
@@ -384,8 +385,8 @@ internal static class Output
}
// All averages are over per-op µs (iter-independent). Three aggregations per metric.
- 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 sizeAcResults = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen)).ToList();
+ var sizeMpResults = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray)).ToList();
var serStats = ComputeOverallStats(acBinarySerResults, memPackSerResults, SerPerOp);
var desStats = ComputeOverallStats(acBinaryDesResults, memPackDesResults, DesPerOp);
@@ -472,7 +473,7 @@ internal static class Output
var testResults = results.Where(r => r.TestDataName == testData.DisplayName).ToList();
foreach (var result in testResults)
{
- sb.AppendLine($"{result.TestDataName},{result.Engine},{result.IoMode},{result.DispatchMode},{result.OptionsPreset},{result.SerializedSize},{SerPerOp(result):F2},{DesPerOp(result):F2},{RtPerOp(result):F2},{result.SerializeAllocBytesPerOp},{result.DeserializeAllocBytesPerOp},{result.RoundTripAllocBytesPerOp},{result.SetupSerializeAllocBytes},{result.SetupDeserializeAllocBytes}");
+ sb.AppendLine($"{result.TestDataName},{result.Engine.ToDisplay()},{result.IoMode.ToDisplay()},{result.DispatchMode.ToDisplay()},{result.OptionsPreset},{result.SerializedSize},{SerPerOp(result):F2},{DesPerOp(result):F2},{RtPerOp(result):F2},{result.SerializeAllocBytesPerOp},{result.DeserializeAllocBytesPerOp},{result.RoundTripAllocBytesPerOp},{result.SetupSerializeAllocBytes},{result.SetupDeserializeAllocBytes}");
}
}
sb.AppendLine();
@@ -486,8 +487,8 @@ internal static class Output
{
// 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 == Configuration.EngineMemoryPack && r.IoMode == Configuration.IoByteArray));
- var acBinaryResult = testResults.FirstOrDefault(r => (r.Engine == Configuration.EngineAcBinary && r.IoMode == Configuration.IoByteArray && r.DispatchMode == Configuration.ModeSGen));
+ var memPackResult = testResults.FirstOrDefault(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray));
+ var acBinaryResult = testResults.FirstOrDefault(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen));
sb.AppendLine();
sb.AppendLine($"--- {testData.DisplayName} ---");
@@ -497,7 +498,7 @@ internal static class Output
var rank = 1;
foreach (var result in testResults)
{
- var isHighlighted = ((result.Engine == Configuration.EngineMemoryPack || result.Engine == Configuration.EngineAcBinary) && result.IoMode == Configuration.IoByteArray);
+ var isHighlighted = ((result.Engine == BenchmarkEngine.MemoryPack || result.Engine == BenchmarkEngine.AcBinary) && result.IoMode == BenchmarkIoMode.ByteArray);
var prefix = isHighlighted ? "► " : " ";
var size = $"{result.SerializedSize:N0}";
@@ -527,13 +528,13 @@ internal static class Output
sb.AppendLine();
sb.AppendLine("=== AcBinary (Byte[], SGen) vs MemoryPack (Byte[]) (Overall) ===");
- 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 memPackSerResults2 = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray) && r.SerializeTimeMs > 0).ToList();
+ var memPackDesResults2 = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray) && r.DeserializeTimeMs > 0).ToList();
+ var memPackRtResults2 = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray) && 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();
+ var acBinarySerResults2 = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen) && r.SerializeTimeMs > 0).ToList();
+ var acBinaryDesResults2 = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen) && r.DeserializeTimeMs > 0).ToList();
+ var acBinaryRtResults2 = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen) && r.RoundTripTimeMs > 0).ToList();
// Skip comparison block if either side has no Byte[] data
if (memPackRtResults2.Count == 0 || acBinaryRtResults2.Count == 0)
@@ -547,8 +548,8 @@ internal static class Output
return;
}
- 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();
+ var sizeAcResults2 = results.Where(r => (r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen)).ToList();
+ var sizeMpResults2 = results.Where(r => (r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray)).ToList();
AppendOverallLine(sb, "Serialize", "µs/op", ComputeOverallStats(acBinarySerResults2, memPackSerResults2, SerPerOp));
AppendOverallLine(sb, "Ser Alloc", "B/op", ComputeOverallStats(acBinarySerResults2, memPackSerResults2, r => r.SerializeAllocBytesPerOp), "F0");
@@ -620,13 +621,13 @@ internal static class Output
var iterCol = r.IsRoundTripOnly
? r.RoundTripIterations.ToString(inv)
: $"{(r.SerializeIterations > 0 ? r.SerializeIterations.ToString(inv) : "-")} / {(r.DeserializeIterations > 0 ? r.DeserializeIterations.ToString(inv) : "-")}";
- sb.AppendLine($"{r.TestDataName} | {r.Engine} | {r.IoMode} | {r.DispatchMode} | {r.OptionsPreset} | {r.SerializedSize} | {ser} | {des} | {rt} | {serAlloc} | {desAlloc} | {rtAlloc} | {setupAlloc} | {iterCol}");
+ sb.AppendLine($"{r.TestDataName} | {r.Engine.ToDisplay()} | {r.IoMode.ToDisplay()} | {r.DispatchMode.ToDisplay()} | {r.OptionsPreset} | {r.SerializedSize} | {ser} | {des} | {rt} | {serAlloc} | {desAlloc} | {rtAlloc} | {setupAlloc} | {iterCol}");
}
}
// Overall AcBinary (SGen, Byte[]) vs MemoryPack (Byte[]) comparison
- 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 memPackByteArrayResults = results.Where(r => r.Engine == BenchmarkEngine.MemoryPack && r.IoMode == BenchmarkIoMode.ByteArray).ToList();
+ var acBinarySGenByteArrayResults = results.Where(r => r.Engine == BenchmarkEngine.AcBinary && r.IoMode == BenchmarkIoMode.ByteArray && r.DispatchMode == BenchmarkDispatchMode.SGen).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();