[LOADED_DOCS: 2 files, no new loads]

Optimize FastWire string (de)serialization and benchmarks

- Increased release benchmark iterations for more robust testing.
- Improved FastWire string deserialization with zero-copy UTF-16.
- Set FastWire and string caching options during context init/reset.
- Optimized FastWire string serialization for direct UTF-16 copy.
- Enhanced non-ASCII string fallback to use Utf8NoBom encoding.
- Refactored WriteFixStr for efficient ASCII and fallback handling.
This commit is contained in:
Loretta 2026-05-04 07:32:29 +02:00
parent 80235c9a3d
commit 2c73775389
4 changed files with 26 additions and 16 deletions

View File

@ -45,8 +45,8 @@ public static class Program
private static int TestIterations = 1; private static int TestIterations = 1;
private static int BenchmarkSamples = 1; // Debug: single sample, fast iteration private static int BenchmarkSamples = 1; // Debug: single sample, fast iteration
#else #else
private static int WarmupIterations = 100; //5000 private static int WarmupIterations = 5000; //5000
private static int TestIterations = 10; //1000 private static int TestIterations = 1000; //1000
private static int BenchmarkSamples = 3; private static int BenchmarkSamples = 3;
#endif #endif

View File

@ -365,8 +365,10 @@ public static partial class AcBinaryDeserializer
{ {
var byteLen = length * 2; var byteLen = length * 2;
EnsureAvailable(byteLen); EnsureAvailable(byteLen);
var chars = MemoryMarshal.Cast<byte, char>(_buffer.AsSpan(_position, byteLen)); var chars = MemoryMarshal.Cast<byte, char>(_buffer.AsSpan(_position, byteLen));
var value = new string(chars); var value = new string(chars);
_position += byteLen; _position += byteLen;
return value; return value;
} }

View File

@ -152,13 +152,17 @@ public static partial class AcBinaryDeserializer
{ {
Input = input; Input = input;
Input.Initialize(out _buffer, out _position, out _bufferLength); Input.Initialize(out _buffer, out _position, out _bufferLength);
_useStringCaching = Options.UseStringCaching; _useStringCaching = Options.UseStringCaching;
_maxCachedStringLength = Options.MaxCachedStringLength; _maxCachedStringLength = Options.MaxCachedStringLength;
if (_useStringCaching) GetOrCreateStringCache(); if (_useStringCaching) GetOrCreateStringCache();
_internCache = null; _internCache = null;
HasMetadata = false; HasMetadata = false;
IsMergeMode = false; IsMergeMode = false;
RemoveOrphanedItems = false; RemoveOrphanedItems = false;
FastWire = Options.WireMode == WireMode.Fast; FastWire = Options.WireMode == WireMode.Fast;
ChainTracker = null; ChainTracker = null;
} }

View File

@ -290,9 +290,12 @@ public static partial class AcBinarySerializer
{ {
// IMPORTANT: base.Reset sets Options first, so derived code can use Options-derived properties // IMPORTANT: base.Reset sets Options first, so derived code can use Options-derived properties
base.Reset(options); base.Reset(options);
HasPropertyFilter = Options.PropertyFilter != null; HasPropertyFilter = Options.PropertyFilter != null;
InternBit = 1 << (int)Options.UseStringInterning; InternBit = 1 << (int)Options.UseStringInterning;
HasStringInterning = Options.UseStringInterning != StringInterningMode.None; HasStringInterning = Options.UseStringInterning != StringInterningMode.None;
FastWire = Options.WireMode == WireMode.Fast; FastWire = Options.WireMode == WireMode.Fast;
} }
@ -658,10 +661,13 @@ public static partial class AcBinarySerializer
// UTF-16: char count (VarUInt) + raw char data (zero-encoding memcopy) // UTF-16: char count (VarUInt) + raw char data (zero-encoding memcopy)
var charLen = value.Length; var charLen = value.Length;
var byteLen = charLen * 2; var byteLen = charLen * 2;
WriteVarUInt((uint)charLen); WriteVarUInt((uint)charLen);
EnsureCapacity(byteLen); EnsureCapacity(byteLen);
MemoryMarshal.AsBytes(value.AsSpan()).CopyTo(_buffer.AsSpan(_position, byteLen)); MemoryMarshal.AsBytes(value.AsSpan()).CopyTo(_buffer.AsSpan(_position, byteLen));
_position += byteLen; _position += byteLen;
return; return;
} }
@ -681,9 +687,11 @@ public static partial class AcBinarySerializer
// Non-ASCII fallback: safe rewind (no Grow happened since pre-allocate) // Non-ASCII fallback: safe rewind (no Grow happened since pre-allocate)
_position = savedPosition; _position = savedPosition;
var byteCount = Utf8NoBom.GetByteCount(value); var byteCount = Utf8NoBom.GetByteCount(value);
EnsureCapacity(VarUIntSize((uint)byteCount) + byteCount); EnsureCapacity(VarUIntSize((uint)byteCount) + byteCount);
WriteVarUIntUnsafe((uint)byteCount); WriteVarUIntUnsafe((uint)byteCount);
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount)); Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
_position += byteCount; _position += byteCount;
} }
@ -699,25 +707,21 @@ public static partial class AcBinarySerializer
public void WriteFixStrDirect(string value) public void WriteFixStrDirect(string value)
{ {
// Compute UTF-8 byte count up front. FixStr opcode encodes byte count (5-bit field, ≤31). var length = value.Length;
// For ASCII: byte count = char count; for non-ASCII: byte count > char count. EnsureCapacity(1 + length);
// Bonus over the prior ASCII-only path: short non-ASCII strings (e.g. 8-char Hungarian
// ≈ 12 bytes) now also fit in FixStr and save the String-marker + VarUInt overhead. var destSpan = _buffer.AsSpan(_position + 1, length);
var byteCount = Utf8NoBom.GetByteCount(value); var status = Ascii.FromUtf16(value.AsSpan(), destSpan, out var bytesWritten);
if (byteCount <= BinaryTypeCode.FixStrMaxLength)
if (status == OperationStatus.Done && bytesWritten == length)
{ {
EnsureCapacity(1 + byteCount); _buffer[_position] = BinaryTypeCode.EncodeFixStr(length);
_buffer[_position++] = BinaryTypeCode.EncodeFixStr(byteCount); _position += 1 + length;
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
_position += byteCount;
} }
else else
{ {
_buffer[_position++] = BinaryTypeCode.String; _buffer[_position++] = BinaryTypeCode.String;
EnsureCapacity(VarUIntSize((uint)byteCount) + byteCount); WriteStringUtf8Internal(value);
WriteVarUIntUnsafe((uint)byteCount);
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
_position += byteCount;
} }
} }