From 2c737753890634b96e55ebb0973ce685a1586248 Mon Sep 17 00:00:00 2001 From: Loretta Date: Mon, 4 May 2026 07:32:29 +0200 Subject: [PATCH] [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. --- AyCode.Core.Serializers.Console/Program.cs | 4 +-- ...lizer.BinaryDeserializationContext.Read.cs | 2 ++ ...serializer.BinaryDeserializationContext.cs | 4 +++ ...rySerializer.BinarySerializationContext.cs | 32 +++++++++++-------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs index bbe6140..3fb509e 100644 --- a/AyCode.Core.Serializers.Console/Program.cs +++ b/AyCode.Core.Serializers.Console/Program.cs @@ -45,8 +45,8 @@ public static class Program private static int TestIterations = 1; private static int BenchmarkSamples = 1; // Debug: single sample, fast iteration #else - private static int WarmupIterations = 100; //5000 - private static int TestIterations = 10; //1000 + private static int WarmupIterations = 5000; //5000 + private static int TestIterations = 1000; //1000 private static int BenchmarkSamples = 3; #endif diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs index ce49fd2..346330b 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs @@ -365,8 +365,10 @@ public static partial class AcBinaryDeserializer { var byteLen = length * 2; EnsureAvailable(byteLen); + var chars = MemoryMarshal.Cast(_buffer.AsSpan(_position, byteLen)); var value = new string(chars); + _position += byteLen; return value; } diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs index 78ba0ef..a990200 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs @@ -152,13 +152,17 @@ public static partial class AcBinaryDeserializer { Input = input; Input.Initialize(out _buffer, out _position, out _bufferLength); + _useStringCaching = Options.UseStringCaching; _maxCachedStringLength = Options.MaxCachedStringLength; + if (_useStringCaching) GetOrCreateStringCache(); + _internCache = null; HasMetadata = false; IsMergeMode = false; RemoveOrphanedItems = false; + FastWire = Options.WireMode == WireMode.Fast; ChainTracker = null; } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 07d6ec5..1d3f0ee 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -290,9 +290,12 @@ public static partial class AcBinarySerializer { // IMPORTANT: base.Reset sets Options first, so derived code can use Options-derived properties base.Reset(options); + HasPropertyFilter = Options.PropertyFilter != null; + InternBit = 1 << (int)Options.UseStringInterning; HasStringInterning = Options.UseStringInterning != StringInterningMode.None; + 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) var charLen = value.Length; var byteLen = charLen * 2; + WriteVarUInt((uint)charLen); EnsureCapacity(byteLen); + MemoryMarshal.AsBytes(value.AsSpan()).CopyTo(_buffer.AsSpan(_position, byteLen)); _position += byteLen; + return; } @@ -681,9 +687,11 @@ public static partial class AcBinarySerializer // Non-ASCII fallback: safe rewind (no Grow happened since pre-allocate) _position = savedPosition; + var byteCount = Utf8NoBom.GetByteCount(value); EnsureCapacity(VarUIntSize((uint)byteCount) + byteCount); WriteVarUIntUnsafe((uint)byteCount); + Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount)); _position += byteCount; } @@ -699,25 +707,21 @@ public static partial class AcBinarySerializer public void WriteFixStrDirect(string value) { - // Compute UTF-8 byte count up front. FixStr opcode encodes byte count (5-bit field, ≤31). - // For ASCII: byte count = char count; for non-ASCII: byte count > char count. - // 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 byteCount = Utf8NoBom.GetByteCount(value); - if (byteCount <= BinaryTypeCode.FixStrMaxLength) + 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 == OperationStatus.Done && bytesWritten == length) { - EnsureCapacity(1 + byteCount); - _buffer[_position++] = BinaryTypeCode.EncodeFixStr(byteCount); - Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount)); - _position += byteCount; + _buffer[_position] = BinaryTypeCode.EncodeFixStr(length); + _position += 1 + length; } else { _buffer[_position++] = BinaryTypeCode.String; - EnsureCapacity(VarUIntSize((uint)byteCount) + byteCount); - WriteVarUIntUnsafe((uint)byteCount); - Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount)); - _position += byteCount; + WriteStringUtf8Internal(value); } }