AyCode.Core/.plan

211 lines
7.7 KiB
Plaintext

# 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<TOutput>`-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<TOutput> — ú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<byte>)
4. WriteRaw<T>(T) where T : unmanaged
5. WriteTypeCodeAndRaw<T>(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<byte>)
18. WritePreencodedPropertyName(ReadOnlySpan<byte>)
19. WriteDoubleArrayBulk(double[])
20. WriteFloatArrayBulk(float[])
21. WriteGuidArrayBulk(Guid[])
22. WriteInt32ArrayOptimized(int[])
23. WriteLongArrayOptimized(long[])
24. WriteBytesSimd(ReadOnlySpan<byte>)
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<byte> AsSpan(byte[] buffer, int position);
public byte[] ToArray(byte[] buffer, int position);
public BinarySerializationResult DetachResult(byte[] buffer, int position);
public void WriteTo(IBufferWriter<byte> writer, byte[] buffer, int position);
}
```
## 4. BufferWriterBinaryOutput (~350 → ~100 sor)
```csharp
public sealed class BufferWriterBinaryOutput : BinaryOutputBase
{
private readonly IBufferWriter<byte> _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<TOutput>(int, TOutput output)` → `WriteInt32<TOutput>(int, BinarySerializationContext<TOutput> context)`
- `WriteString<TOutput>(string, TOutput output, context)` → `WriteString<TOutput>(string, BinarySerializationContext<TOutput> 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<TOutput> context` param (2 JIT copy elfogadható, per-array hívás)
### Public API metódusok:
```csharp
// Serialize<T> (byte[]):
context.Output.Initialize(out context._buffer, out context._position, out context._bufferEnd);
// ... serialize ...
return context.Output.ToArray(context._buffer, context._position);
// Serialize<T> (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)