using AyCode.Core.Serializers.Binaries; using AyCode.Core.Tests.TestModels; using System.Buffers; using System.Runtime.CompilerServices; namespace AyCode.Core.Benchmarks.Workloads.Scenarios; /// /// Benchmarks AcBinary via the IBufferWriter overload with a pre-allocated, reused ArrayBufferWriter. /// Realistic IBufferWriter usage pattern: caller owns + reuses the writer (zero alloc per call after warmup). /// public sealed class AcBinaryBufferWriterBenchmark : ISerializerBenchmark where T : class { private readonly T _order; private readonly AcBinarySerializerOptions _options; private readonly byte[] _serialized; private readonly ArrayBufferWriter _bufferWriter; public BenchmarkEngine Engine => BenchmarkEngine.AcBinary; public BenchmarkIoMode IoMode => BenchmarkIoMode.BufWrReuse; public BenchmarkDispatchMode DispatchMode => _options.UseGeneratedCode ? BenchmarkDispatchMode.SGen : BenchmarkDispatchMode.Runtime; public Type OrderType => typeof(T); public string OptionsPreset { get; } public int SerializedSize => _serialized.Length; public long SetupSerializeAllocBytes { get; } public long SetupDeserializeAllocBytes => 0; public string OptionsDescription => BenchmarkOptions.BuildAcBinary(_options); public AcBinaryBufferWriterBenchmark(T order, AcBinarySerializerOptions options, string optionsPreset) { _order = order; _options = options; OptionsPreset = optionsPreset; _serialized = AcBinarySerializer.Serialize(order, options); // Measure ONLY the BufferWriter infrastructure setup on the serialize side (excluding the // helper Serialize above). Deserialize side reads directly from `_serialized` byte[] — no // dedicated setup allocation, hence SetupDeserializeAllocBytes = 0. GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); var beforeSetup = GC.GetAllocatedBytesForCurrentThread(); _bufferWriter = new ArrayBufferWriter(_serialized.Length * 2); var afterSetup = GC.GetAllocatedBytesForCurrentThread(); SetupSerializeAllocBytes = afterSetup - beforeSetup; } [MethodImpl(MethodImplOptions.NoInlining)] public void Serialize() { _bufferWriter.ResetWrittenCount(); // reuse — no alloc, no zeroing AcBinarySerializer.Serialize(_order, _bufferWriter, _options); } // BufWr semantic: read from a ReadOnlySequence (the ROS overload), NOT from byte[] — // single-segment array-backed sequence triggers the fast-path in AcBinaryDeserializer.cs:298 which // redirects to the byte[] overload. This means the bench actually exercises the ROS-input path // (the production-realistic surface for SignalR / Pipe consumers) rather than secretly testing // byte[] Deser under the BufWr label. [MethodImpl(MethodImplOptions.NoInlining)] public void Deserialize() => AcBinaryDeserializer.Deserialize(new ReadOnlySequence(_serialized), _options); public bool VerifyRoundTrip() { _bufferWriter.ResetWrittenCount(); AcBinarySerializer.Serialize(_order, _bufferWriter, _options); var roundTripped = AcBinaryDeserializer.Deserialize(new ReadOnlySequence(_bufferWriter.WrittenMemory), _options); return RoundTripValidator.DeepEqualsViaJson(_order, roundTripped); } }