diff --git a/AyCode.Core/Serializers/AcSerializerOptions.cs b/AyCode.Core/Serializers/AcSerializerOptions.cs index 745868a..16adedb 100644 --- a/AyCode.Core/Serializers/AcSerializerOptions.cs +++ b/AyCode.Core/Serializers/AcSerializerOptions.cs @@ -114,6 +114,22 @@ public enum ReferenceHandlingMode : byte All = 2 } +/// +/// Wire encoding mode for binary serialization. +/// +public enum WireMode : byte +{ + /// + /// Compact encoding: VarInt for integers, UTF-8 for strings. Smaller output. + /// + Compact = 0, + + /// + /// Fast encoding: fixed-width integers, UTF-16 for strings. Larger output, faster encode/decode. + /// + Fast = 1 +} + /// /// Delegate for custom property mapping during cross-type deserialization/population. /// Enables mapping between different class hierarchies or renamed properties. diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs index f224a6d..ccf5b10 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs @@ -12,7 +12,7 @@ public static partial class AcBinaryDeserializer { private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - #region Buffer State — owned by context for zero virtual dispatch + #region Buffer State � owned by context for zero virtual dispatch internal byte[] _buffer = null!; internal int _bufferLength; @@ -20,7 +20,7 @@ public static partial class AcBinaryDeserializer #endregion - // String caching state — needed for WASM optimization + // String caching state � needed for WASM optimization // The cache dictionary is owned by context (pooled), passed in at init time. private bool _useStringCaching; private int _maxCachedStringLength; @@ -172,6 +172,16 @@ public static partial class AcBinaryDeserializer return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T ReadRaw() where T : unmanaged + { + var size = Unsafe.SizeOf(); + EnsureAvailable(size); + var value = Unsafe.ReadUnaligned(ref _buffer[_position]); + _position += size; + return value; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadInt32Raw() { @@ -188,6 +198,7 @@ public static partial class AcBinaryDeserializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadVarInt() { + if (FastWire) { return ReadRaw(); } var raw = ReadVarUInt(); var value = (int)(raw >> 1) ^ -(int)(raw & 1); return value; @@ -196,6 +207,7 @@ public static partial class AcBinaryDeserializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint ReadVarUInt() { + if (FastWire) { return ReadRaw(); } // Fast path: single byte (0-127) - ~70% of cases var b0 = _buffer[_position]; if ((b0 & 0x80) == 0) @@ -245,6 +257,7 @@ public static partial class AcBinaryDeserializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadVarLong() { + if (FastWire) { return ReadRaw(); } var raw = ReadVarULong(); var value = (long)(raw >> 1) ^ -((long)raw & 1); return value; @@ -253,6 +266,7 @@ public static partial class AcBinaryDeserializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadVarULong() { + if (FastWire) { return ReadRaw(); } ulong value = 0; var shift = 0; while (true) @@ -277,7 +291,7 @@ public static partial class AcBinaryDeserializer #endregion /// - /// Called on first StringInternFirst marker — disables _stringCache because + /// Called on first StringInternFirst marker � disables _stringCache because /// interned strings are resolved via _internCache and plain strings appear only once. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -311,6 +325,17 @@ public static partial class AcBinaryDeserializer return string.Empty; } + // FastWire: length is char count, data is UTF-16 (2 bytes per char) + if (FastWire) + { + var byteLen = length * 2; + EnsureAvailable(byteLen); + var chars = MemoryMarshal.Cast(_buffer.AsSpan(_position, byteLen)); + var value = new string(chars); + _position += byteLen; + return value; + } + EnsureAvailable(length); // WASM optimization: cache short strings to reduce allocations @@ -319,8 +344,8 @@ public static partial class AcBinaryDeserializer return ReadStringUtf8Cached(length); } - // ASCII fast path: short strings (?128 bytes) with all ASCII bytes - // use string.Create + direct byte?char widening, avoiding UTF8Encoding overhead. + // ASCII fast path: short strings (≤128 bytes) with all ASCII bytes + // use string.Create + direct byte→char widening, avoiding UTF8Encoding overhead. if (length <= 128 && System.Text.Ascii.IsValid(_buffer.AsSpan(_position, length))) { var pos = _position; @@ -333,9 +358,9 @@ public static partial class AcBinaryDeserializer }); } - var value = Utf8NoBom.GetString(_buffer, _position, length); + var value2 = Utf8NoBom.GetString(_buffer, _position, length); _position += length; - return value; + return value2; } private string ReadStringUtf8Cached(int length) @@ -366,7 +391,7 @@ public static partial class AcBinaryDeserializer /// /// Full-content hash for string caching. - /// CRITICAL: DO NOT SIMPLIFY — prevents hash collisions for similar property names. + /// CRITICAL: DO NOT SIMPLIFY � prevents hash collisions for similar property names. /// See BinaryDeserializationContext for full history. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs index efc200b..4e1c736 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs @@ -63,6 +63,7 @@ public static partial class AcBinaryDeserializer public bool HasMetadata; public bool IsMergeMode; public bool RemoveOrphanedItems; + public bool FastWire; // Options-derived properties public byte MinStringInternLength => Options.MinStringInternLength; @@ -138,6 +139,7 @@ public static partial class AcBinaryDeserializer 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 8cfe96e..92759f1 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -227,6 +227,7 @@ public static partial class AcBinarySerializer /// Reference handling is safe because generated code inlines TryConsumeWritePlanEntry for IId types. /// public bool IsDirectObjectWrite => !UseMetadata && !HasPropertyFilter; + public bool FastWire { get; private set; } public byte MinStringInternLength => Options.MinStringInternLength; public byte MaxStringInternLength => Options.MaxStringInternLength; public BinaryPropertyFilter? PropertyFilter => Options.PropertyFilter; @@ -249,6 +250,7 @@ public static partial class AcBinarySerializer public BinarySerializationContext(AcBinarySerializerOptions options) { Reset(options); + FastWire = options.WireMode == WireMode.Fast; } /// @@ -262,6 +264,7 @@ 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; + FastWire = Options.WireMode == WireMode.Fast; } public override void Clear() @@ -386,6 +389,7 @@ public static partial class AcBinarySerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarUInt(uint value) { + if (FastWire) { WriteRaw(value); return; } if (value < 0x80) { if (_position >= _bufferEnd) @@ -405,6 +409,7 @@ public static partial class AcBinarySerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarInt(int value) { + if (FastWire) { WriteRaw(value); return; } var encoded = (uint)((value << 1) ^ (value >> 31)); WriteVarUInt(encoded); } @@ -412,6 +417,7 @@ public static partial class AcBinarySerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarULong(ulong value) { + if (FastWire) { WriteRaw(value); return; } if (value < 0x80) { if (_position >= _bufferEnd) @@ -431,6 +437,7 @@ public static partial class AcBinarySerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVarLong(long value) { + if (FastWire) { WriteRaw(value); return; } var encoded = (ulong)((value << 1) ^ (value >> 63)); WriteVarULong(encoded); } @@ -481,6 +488,18 @@ public static partial class AcBinarySerializer public void WriteStringUtf8(string value) { + if (FastWire) + { + // UTF-16: char count (fixed uint) + raw char data (zero-encoding memcopy) + var charLen = value.Length; + var byteLen = charLen * 2; + WriteRaw(charLen); + EnsureCapacity(byteLen); + MemoryMarshal.AsBytes(value.AsSpan()).CopyTo(_buffer.AsSpan(_position, byteLen)); + _position += byteLen; + return; + } + var charLength = value.Length; // Speculative ASCII fast path: assume byteCount == charLength diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index 22041b8..3e2de6a 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -970,6 +970,14 @@ public static partial class AcBinarySerializer #endif } + // FastWire: skip FixStr optimization (UTF-8 specific), write String marker + UTF-16 data + if (context.FastWire) + { + context.WriteByte(BinaryTypeCode.String); + context.WriteStringUtf8(value); + return; + } + // Fast path for short strings: check length first (cheap), then ASCII // FixStr encodes type+length in single byte for strings <= 31 chars var length = value.Length; diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs index 654d25e..a96eb42 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs @@ -95,6 +95,13 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions /// public StringInterningMode UseStringInterning { get; set; } = StringInterningMode.Attribute; + /// + /// Wire encoding mode. + /// Compact: VarInt + UTF-8 (default, smaller output). + /// Fast: Fixed-width integers + UTF-16 (larger output, faster encode/decode). + /// + public WireMode WireMode { get; set; } = WireMode.Fast; + /// /// When true, checks for duplicate property name hashes during serialization (UseMetadata mode). /// Throws exception if FNV-1a hash collision is detected between property names of the same type.