416 lines
13 KiB
C#
416 lines
13 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace AyCode.Core.Serializers.Binaries;
|
|
|
|
/// <summary>
|
|
/// High-performance binary output backed by a byte[] from ArrayPool.
|
|
/// Matches the exact performance characteristics of the original BinarySerializationContext buffer code:
|
|
/// direct _buffer[_position++] indexing, Unsafe.WriteUnaligned, SIMD bulk copy.
|
|
///
|
|
/// This is the fastest output path — use when the result is needed as byte[]/Span.
|
|
/// </summary>
|
|
public sealed class ArrayBinaryOutput : BinaryOutputBase, IDisposable
|
|
{
|
|
private const int MinBufferSize = 256;
|
|
|
|
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
|
|
|
private byte[] _buffer;
|
|
private int _position;
|
|
|
|
public ArrayBinaryOutput(int initialCapacity = 4096)
|
|
{
|
|
_buffer = ArrayPool<byte>.Shared.Rent(Math.Max(initialCapacity, MinBufferSize));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int Position
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => _position;
|
|
}
|
|
|
|
#region Abstract Overrides — Core Primitives
|
|
|
|
/// <inheritdoc />
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteByte(byte value)
|
|
{
|
|
if (_position >= _buffer.Length)
|
|
GrowBuffer(_position + 1);
|
|
_buffer[_position++] = value;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteTwoBytes(byte b1, byte b2)
|
|
{
|
|
EnsureCapacity(2);
|
|
_buffer[_position++] = b1;
|
|
_buffer[_position++] = b2;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteBytes(ReadOnlySpan<byte> data)
|
|
{
|
|
EnsureCapacity(data.Length);
|
|
data.CopyTo(_buffer.AsSpan(_position));
|
|
_position += data.Length;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteRaw<T>(T value)
|
|
{
|
|
var size = Unsafe.SizeOf<T>();
|
|
EnsureCapacity(size);
|
|
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
|
_position += size;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected override void EnsureCapacity(int additionalBytes)
|
|
{
|
|
var required = _position + additionalBytes;
|
|
if (required <= _buffer.Length)
|
|
return;
|
|
GrowBuffer(required);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private void GrowBuffer(int required)
|
|
{
|
|
var newSize = Math.Max(_buffer.Length * 2, required);
|
|
var newBuffer = ArrayPool<byte>.Shared.Rent(newSize);
|
|
_buffer.AsSpan(0, _position).CopyTo(newBuffer);
|
|
ArrayPool<byte>.Shared.Return(_buffer);
|
|
_buffer = newBuffer;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Optimized Overrides — Specialized Types (direct buffer access)
|
|
|
|
/// <summary>
|
|
/// Optimized: single EnsureCapacity + direct Unsafe.WriteUnaligned + indexer.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteTypeCodeAndRaw<T>(byte typeCode, T value)
|
|
{
|
|
var size = 1 + Unsafe.SizeOf<T>();
|
|
EnsureCapacity(size);
|
|
_buffer[_position++] = typeCode;
|
|
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
|
_position += Unsafe.SizeOf<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: VarUInt with direct _buffer[_position++] access.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteVarUInt(uint value)
|
|
{
|
|
if (value < 0x80)
|
|
{
|
|
if (_position >= _buffer.Length)
|
|
GrowBuffer(_position + 1);
|
|
_buffer[_position++] = (byte)value;
|
|
return;
|
|
}
|
|
EnsureCapacity(5);
|
|
while (value >= 0x80)
|
|
{
|
|
_buffer[_position++] = (byte)(value | 0x80);
|
|
value >>= 7;
|
|
}
|
|
_buffer[_position++] = (byte)value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: VarInt with direct buffer access.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteVarInt(int value)
|
|
{
|
|
var encoded = (uint)((value << 1) ^ (value >> 31));
|
|
WriteVarUInt(encoded);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: VarULong with direct buffer access.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteVarULong(ulong value)
|
|
{
|
|
if (value < 0x80)
|
|
{
|
|
if (_position >= _buffer.Length)
|
|
GrowBuffer(_position + 1);
|
|
_buffer[_position++] = (byte)value;
|
|
return;
|
|
}
|
|
EnsureCapacity(10);
|
|
while (value >= 0x80)
|
|
{
|
|
_buffer[_position++] = (byte)(value | 0x80);
|
|
value >>= 7;
|
|
}
|
|
_buffer[_position++] = (byte)value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: VarLong with direct buffer access.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteVarLong(long value)
|
|
{
|
|
var encoded = (ulong)((value << 1) ^ (value >> 63));
|
|
WriteVarULong(encoded);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: direct Unsafe.WriteUnaligned for decimal bits.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteDecimalBits(decimal value)
|
|
{
|
|
EnsureCapacity(16);
|
|
Span<int> bits = stackalloc int[4];
|
|
decimal.TryGetBits(value, bits, out _);
|
|
MemoryMarshal.AsBytes(bits).CopyTo(_buffer.AsSpan(_position, 16));
|
|
_position += 16;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: direct Unsafe.WriteUnaligned + indexer for DateTime.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteDateTimeBits(DateTime value)
|
|
{
|
|
EnsureCapacity(9);
|
|
Unsafe.WriteUnaligned(ref _buffer[_position], value.Ticks);
|
|
_buffer[_position + 8] = (byte)value.Kind;
|
|
_position += 9;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: direct TryWriteBytes into buffer.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteGuidBits(Guid value)
|
|
{
|
|
EnsureCapacity(16);
|
|
value.TryWriteBytes(_buffer.AsSpan(_position, 16));
|
|
_position += 16;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: direct Unsafe.WriteUnaligned for DateTimeOffset.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteDateTimeOffsetBits(DateTimeOffset value)
|
|
{
|
|
EnsureCapacity(10);
|
|
Unsafe.WriteUnaligned(ref _buffer[_position], value.UtcTicks);
|
|
Unsafe.WriteUnaligned(ref _buffer[_position + 8], (short)value.Offset.TotalMinutes);
|
|
_position += 10;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: direct ASCII fast path into _buffer.
|
|
/// </summary>
|
|
public override void WriteStringUtf8(string value)
|
|
{
|
|
if (Ascii.IsValid(value))
|
|
{
|
|
WriteVarUInt((uint)value.Length);
|
|
EnsureCapacity(value.Length);
|
|
Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, value.Length), out _);
|
|
_position += value.Length;
|
|
return;
|
|
}
|
|
|
|
var byteCount = Utf8NoBom.GetByteCount(value);
|
|
WriteVarUInt((uint)byteCount);
|
|
EnsureCapacity(byteCount);
|
|
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
|
|
_position += byteCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: FixStr with direct buffer write.
|
|
/// </summary>
|
|
public override void WriteFixStr(string value)
|
|
{
|
|
var length = value.Length;
|
|
EnsureCapacity(1 + length);
|
|
_buffer[_position++] = BinaryTypeCode.EncodeFixStr(length);
|
|
Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, length), out _);
|
|
_position += length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: FixStrDirect with SIMD try into buffer.
|
|
/// </summary>
|
|
public override void WriteFixStrDirect(string value)
|
|
{
|
|
var length = value.Length;
|
|
EnsureCapacity(1 + length);
|
|
|
|
var destSpan = _buffer.AsSpan(_position + 1, length);
|
|
var status = Ascii.FromUtf16(value.AsSpan(), destSpan, out var bytesWritten);
|
|
|
|
if (status == System.Buffers.OperationStatus.Done && bytesWritten == length)
|
|
{
|
|
_buffer[_position] = BinaryTypeCode.EncodeFixStr(length);
|
|
_position += 1 + length;
|
|
}
|
|
else
|
|
{
|
|
_buffer[_position++] = BinaryTypeCode.String;
|
|
WriteStringUtf8Internal(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optimized: FixStrBytes with direct buffer copy.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void WriteFixStrBytes(ReadOnlySpan<byte> utf8Bytes)
|
|
{
|
|
var length = utf8Bytes.Length;
|
|
EnsureCapacity(1 + length);
|
|
_buffer[_position++] = BinaryTypeCode.EncodeFixStr(length);
|
|
utf8Bytes.CopyTo(_buffer.AsSpan(_position, length));
|
|
_position += length;
|
|
}
|
|
|
|
private void WriteStringUtf8Internal(string value)
|
|
{
|
|
var byteCount = Utf8NoBom.GetByteCount(value);
|
|
WriteVarUInt((uint)byteCount);
|
|
EnsureCapacity(byteCount);
|
|
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
|
|
_position += byteCount;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Optimized Overrides — Bulk Arrays (direct buffer, batched capacity)
|
|
|
|
/// <inheritdoc />
|
|
public override void WriteDoubleArrayBulk(double[] array)
|
|
{
|
|
EnsureCapacity(array.Length * 9);
|
|
for (var i = 0; i < array.Length; i++)
|
|
{
|
|
_buffer[_position++] = BinaryTypeCode.Float64;
|
|
Unsafe.WriteUnaligned(ref _buffer[_position], array[i]);
|
|
_position += 8;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void WriteFloatArrayBulk(float[] array)
|
|
{
|
|
EnsureCapacity(array.Length * 5);
|
|
for (var i = 0; i < array.Length; i++)
|
|
{
|
|
_buffer[_position++] = BinaryTypeCode.Float32;
|
|
Unsafe.WriteUnaligned(ref _buffer[_position], array[i]);
|
|
_position += 4;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void WriteGuidArrayBulk(Guid[] array)
|
|
{
|
|
EnsureCapacity(array.Length * 17);
|
|
for (var i = 0; i < array.Length; i++)
|
|
{
|
|
_buffer[_position++] = BinaryTypeCode.Guid;
|
|
array[i].TryWriteBytes(_buffer.AsSpan(_position, 16));
|
|
_position += 16;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void WriteBytesSimd(ReadOnlySpan<byte> source)
|
|
{
|
|
EnsureCapacity(source.Length);
|
|
var destination = _buffer.AsSpan(_position, source.Length);
|
|
|
|
if (Vector.IsHardwareAccelerated && source.Length >= Vector<byte>.Count * 2)
|
|
{
|
|
var vectorSize = Vector<byte>.Count;
|
|
var i = 0;
|
|
var length = source.Length;
|
|
var vectorCount = length / vectorSize;
|
|
for (var v = 0; v < vectorCount; v++)
|
|
{
|
|
var vec = new Vector<byte>(source.Slice(i, vectorSize));
|
|
vec.CopyTo(destination.Slice(i, vectorSize));
|
|
i += vectorSize;
|
|
}
|
|
if (i < length)
|
|
source.Slice(i).CopyTo(destination.Slice(i));
|
|
}
|
|
else
|
|
{
|
|
source.CopyTo(destination);
|
|
}
|
|
|
|
_position += source.Length;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Output Methods
|
|
|
|
/// <summary>Returns the written data as a ReadOnlySpan without allocation.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public ReadOnlySpan<byte> AsSpan() => _buffer.AsSpan(0, _position);
|
|
|
|
/// <summary>Copies the written data to a new exactly-sized array.</summary>
|
|
public byte[] ToArray()
|
|
{
|
|
var result = GC.AllocateUninitializedArray<byte>(_position);
|
|
_buffer.AsSpan(0, _position).CopyTo(result);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Copies the written data to an IBufferWriter (single memcpy).</summary>
|
|
public void WriteTo(IBufferWriter<byte> writer)
|
|
{
|
|
var span = writer.GetSpan(_position);
|
|
_buffer.AsSpan(0, _position).CopyTo(span);
|
|
writer.Advance(_position);
|
|
}
|
|
|
|
/// <summary>Resets position for reuse without deallocation.</summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Reset() => _position = 0;
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_buffer != null)
|
|
{
|
|
ArrayPool<byte>.Shared.Return(_buffer);
|
|
_buffer = null!;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|