# Buffer-in-Context terv: `_buffer`/`_position` visszahelyezése a context-be ## Probléma A TOutput generic refaktorálás ~30-40%-os serialization regressziót okozott. Ok: .NET JIT reference type generikusoknál SHARED kódot generál → minden `output.WriteByte()` virtuális dispatch, még sealed osztályoknál is. ## Megoldás `_buffer` + `_position` visszakerül a `BinarySerializationContext`-ba. Minden hot path write metódus inline context metódus lesz: `_buffer[_position++] = value`. A `TOutput Output` kizárólag cold path Grow/Flush-t kezel. --- ## 1. Új BinaryOutputBase (3 absztrakt metódus) A jelenlegi 19 abstract + 9 virtual metódus helyett: ```csharp public abstract class BinaryOutputBase { public abstract void Initialize(out byte[] buffer, out int position, out int bufferEnd); public abstract void Grow(ref byte[] buffer, ref int position, ref int bufferEnd, int needed); public abstract int GetTotalPosition(int currentPosition); } ``` - `Initialize`: kezdeti buffer kiadása - `Grow`: cold path — buffer betelik → ArrayPool.Rent/copy VAGY Advance+GetMemory - `GetTotalPosition`: Position property-hez (cold path, 1x hívás per serialize) - `IBinaryOutput.cs` törlése (nem implementálja többé senki) ## 2. BinarySerializationContext — új mezők + write metódusok ### Új mezők: ```csharp internal byte[] _buffer = null!; internal int _position; internal int _bufferEnd; // writeable terület vége (_position < _bufferEnd) ``` ### Position property: ```csharp public int Position => Output.GetTotalPosition(_position); // ArrayBinaryOutput: return currentPosition (CommittedBytes=0, bufferStart=0) // BufferWriterBinaryOutput: return _committedBytes + (currentPosition - _chunkStart) ``` ### EnsureCapacity (privát): ```csharp [AggressiveInlining] private void EnsureCapacity(int additionalBytes) { if (_position + additionalBytes > _bufferEnd) Output.Grow(ref _buffer, ref _position, ref _bufferEnd, additionalBytes); } ``` ### Write metódusok (mind [AggressiveInlining], az ArrayBinaryOutput implementációiból portolva): 1. WriteByte(byte) 2. WriteTwoBytes(byte, byte) 3. WriteBytes(ReadOnlySpan) 4. WriteRaw(T) where T : unmanaged 5. WriteTypeCodeAndRaw(byte, T) 6. WriteVarUInt(uint) — fast path < 0x80 7. WriteVarInt(int) — ZigZag + WriteVarUInt 8. WriteVarULong(ulong) 9. WriteVarLong(long) 10. WriteDecimalBits(decimal) 11. WriteDateTimeBits(DateTime) 12. WriteGuidBits(Guid) 13. WriteDateTimeOffsetBits(DateTimeOffset) 14. WriteStringUtf8(string) 15. WriteFixStr(string) 16. WriteFixStrDirect(string) 17. WriteFixStrBytes(ReadOnlySpan) 18. WritePreencodedPropertyName(ReadOnlySpan) 19. WriteDoubleArrayBulk(double[]) 20. WriteFloatArrayBulk(float[]) 21. WriteGuidArrayBulk(Guid[]) 22. WriteInt32ArrayOptimized(int[]) 23. WriteLongArrayOptimized(long[]) 24. WriteBytesSimd(ReadOnlySpan) Mind `_buffer[_position++]` pattern-nel — NULLA virtual dispatch a hot path-on. ### WriteHeader / WriteInlineMetadata: - `Output.WriteByte()` → `WriteByte()` (self) - `WriteInlineMetadata` signature: `output` param eltávolítása ## 3. ArrayBinaryOutput (~430 → ~120 sor) ```csharp public sealed class ArrayBinaryOutput : BinaryOutputBase, IDisposable { private byte[] _rentedBuffer; public override void Initialize(out byte[] buffer, out int position, out int bufferEnd) { buffer = _rentedBuffer; position = 0; bufferEnd = _rentedBuffer.Length; } [NoInlining] public override void Grow(ref byte[] buffer, ref int position, ref int bufferEnd, int needed) { // ArrayPool.Rent bigger + copy + return old // position marad, bufferEnd = newBuffer.Length _rentedBuffer = newBuffer; } public override int GetTotalPosition(int currentPosition) => currentPosition; // Eredmény metódusok — buffer/position paramétert kapnak a context-ből: public ReadOnlySpan AsSpan(byte[] buffer, int position); public byte[] ToArray(byte[] buffer, int position); public BinarySerializationResult DetachResult(byte[] buffer, int position); public void WriteTo(IBufferWriter writer, byte[] buffer, int position); } ``` ## 4. BufferWriterBinaryOutput (~350 → ~100 sor) ```csharp public sealed class BufferWriterBinaryOutput : BinaryOutputBase { private readonly IBufferWriter _writer; private int _committedBytes; private int _currentChunkStart; private bool _ownedBuffer; public override void Initialize(out byte[] buffer, out int position, out int bufferEnd) { _committedBytes = 0; AcquireChunk(MinChunkRequest, out buffer, out position, out bufferEnd); _currentChunkStart = position; } [NoInlining] public override void Grow(ref byte[] buffer, ref int position, ref int bufferEnd, int needed) { // 1. Advance current chunk: _writer.Advance(position - _currentChunkStart) // 2. _committedBytes += bytesInChunk // 3. AcquireChunk(needed, out buffer, out position, out bufferEnd) // 4. _currentChunkStart = position } public override int GetTotalPosition(int currentPosition) => _committedBytes + (currentPosition - _currentChunkStart); public void Flush(byte[] buffer, int position) { // Utolsó chunk commit-ja } private void AcquireChunk(int requestSize, out byte[] buffer, out int position, out int bufferEnd) { // GetMemory() + TryGetArray() → buffer=segment.Array, position=segment.Offset // Fallback: ArrayPool.Rent owned buffer } } ``` ## 5. AcBinarySerializer.cs (~130 call site változás) ### Signature változások: - `WriteInt32(int, TOutput output)` → `WriteInt32(int, BinarySerializationContext context)` - `WriteString(string, TOutput output, context)` → `WriteString(string, BinarySerializationContext context)` - Minden helper: `output` param eltávolítása, `context` marad - `var output = context.Output;` sorok törlése - `output.WriteByte(...)` → `context.WriteByte(...)` ### TryWritePrimitiveArrayCore: - Jelenleg non-generic `BinaryOutputBase output` param - Új: generic `BinarySerializationContext context` param (2 JIT copy elfogadható, per-array hívás) ### Public API metódusok: ```csharp // Serialize (byte[]): context.Output.Initialize(out context._buffer, out context._position, out context._bufferEnd); // ... serialize ... return context.Output.ToArray(context._buffer, context._position); // Serialize (IBufferWriter): output.Initialize(out context._buffer, out context._position, out context._bufferEnd); // ... serialize ... output.Flush(context._buffer, context._position); ``` ## 6. ScanPass.cs — NINCS VÁLTOZÁS Már most is csak context-et kap, nem ír semmit. ## 7. IBinaryOutput.cs — TÖRLÉS Senki nem implementálja többé. ## 8. Implementációs sorrend 1. Context: `_buffer`/`_position`/`_bufferEnd` mezők + 24 write metódus hozzáadása 2. BinaryOutputBase: 28 metódus → 3 (Initialize, Grow, GetTotalPosition) 3. ArrayBinaryOutput: egyszerűsítés (Grow + result metódusok) 4. BufferWriterBinaryOutput: egyszerűsítés (Grow + Flush + AcquireChunk) 5. AcBinarySerializer.cs: ~130 hívás átírása output→context 6. Public API: Initialize/Flush/ToArray hívások buffer/position paraméterekkel 7. Context WriteHeader/WriteInlineMetadata: output param eltávolítása 8. IBinaryOutput.cs törlése 9. Build + teszt ## Várható eredmény - Hot path: `_buffer[_position++]` — nulla virtual dispatch (baseline szintű teljesítmény) - Cold path (Grow): 1 virtual call buffer beteltkor → elhanyagolható - Position: 1 virtual call, de csak 1x hívódik per serialize → elhanyagolható - IBufferWriter streaming: 100% megmarad (Grow = Advance + GetMemory)