211 lines
7.7 KiB
Plaintext
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)
|