diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs index 1c036c3..f8ccc90 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs @@ -80,6 +80,25 @@ public static partial class AcBinaryDeserializer RegisterReader(BinaryTypeCode.Array, ReadArray); RegisterReader(BinaryTypeCode.Dictionary, ReadDictionary); RegisterReader(BinaryTypeCode.ByteArray, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ref ctx)); + + // Register FixStr readers (34-65) + for (byte code = BinaryTypeCode.FixStrBase; code <= BinaryTypeCode.FixStrMax; code++) + { + var length = BinaryTypeCode.DecodeFixStrLength(code); + RegisterReader(code, CreateFixStrReader(length)); + } + } + + /// + /// Creates a reader for FixStr with the given length. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TypeReader CreateFixStrReader(int length) + { + if (length == 0) + return static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty; + + return (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadStringUtf8(length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -284,6 +303,13 @@ public static partial class AcBinaryDeserializer return ConvertToTargetType(intValue, targetType); } + // Handle FixStr (short strings with length in type code) + if (BinaryTypeCode.IsFixStr(typeCode)) + { + var length = BinaryTypeCode.DecodeFixStrLength(typeCode); + return length == 0 ? string.Empty : context.ReadStringUtf8(length); + } + var reader = TypeReaders[typeCode]; if (reader != null) { @@ -1130,6 +1156,15 @@ public static partial class AcBinaryDeserializer if (BinaryTypeCode.IsTinyInt(typeCode)) return; + // Handle FixStr (short strings) + if (BinaryTypeCode.IsFixStr(typeCode)) + { + var length = BinaryTypeCode.DecodeFixStrLength(typeCode); + if (length > 0) + context.Skip(length); + return; + } + switch (typeCode) { case BinaryTypeCode.True: diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 986ee48..c98acd8 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -2,8 +2,10 @@ using System; using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; using System.Text; using ReferenceEqualityComparer = AyCode.Core.Serializers.Jsons.ReferenceEqualityComparer; @@ -836,6 +838,96 @@ public static partial class AcBinarySerializer #endregion + #region SIMD Bulk Copy + + /// + /// Copy bytes using SIMD when available, otherwise fall back to standard copy. + /// Optimized for Blazor WASM where Vector operations are supported. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBytesSimd(ReadOnlySpan source) + { + EnsureCapacity(source.Length); + var destination = _buffer.AsSpan(_position, source.Length); + + if (Vector.IsHardwareAccelerated && source.Length >= Vector.Count * 2) + { + CopyWithSimd(source, destination); + } + else + { + source.CopyTo(destination); + } + + _position += source.Length; + } + + /// + /// SIMD-optimized memory copy for large buffers. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyWithSimd(ReadOnlySpan source, Span destination) + { + var vectorSize = Vector.Count; + var i = 0; + var length = source.Length; + + // Process full vectors + var vectorCount = length / vectorSize; + for (var v = 0; v < vectorCount; v++) + { + var vec = new Vector(source.Slice(i, vectorSize)); + vec.CopyTo(destination.Slice(i, vectorSize)); + i += vectorSize; + } + + // Copy remaining bytes + if (i < length) + { + source.Slice(i).CopyTo(destination.Slice(i)); + } + } + + /// + /// Write double array using SIMD bulk copy (no per-element type codes). + /// For use when caller handles type codes separately. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDoubleBulkRaw(ReadOnlySpan values) + { + var byteSpan = MemoryMarshal.AsBytes(values); + WriteBytesSimd(byteSpan); + } + + /// + /// Write float array using SIMD bulk copy. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFloatBulkRaw(ReadOnlySpan values) + { + var byteSpan = MemoryMarshal.AsBytes(values); + WriteBytesSimd(byteSpan); + } + + /// + /// Write Guid array using SIMD bulk copy. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteGuidBulkRaw(ReadOnlySpan values) + { + // Guid is 16 bytes, perfect for SIMD + var byteLength = values.Length * 16; + EnsureCapacity(byteLength); + + for (var i = 0; i < values.Length; i++) + { + values[i].TryWriteBytes(_buffer.AsSpan(_position, 16)); + _position += 16; + } + } + + #endregion + #region Header and Metadata private int _headerPosition; @@ -1094,5 +1186,37 @@ public static partial class AcBinarySerializer } #endregion + + #region FixStr Methods + + /// + /// Write short ASCII string using FixStr encoding (type+length in single byte). + /// Only call when string is ASCII and length <= 31. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFixStr(string value) + { + var length = value.Length; + EnsureCapacity(1 + length); + _buffer[_position++] = BinaryTypeCode.EncodeFixStr(length); + System.Text.Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, length), out _); + _position += length; + } + + /// + /// Write short UTF8 bytes using FixStr encoding. + /// Only call when byteLength <= 31. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFixStrBytes(ReadOnlySpan utf8Bytes) + { + var length = utf8Bytes.Length; + EnsureCapacity(1 + length); + _buffer[_position++] = BinaryTypeCode.EncodeFixStr(length); + utf8Bytes.CopyTo(_buffer.AsSpan(_position, length)); + _position += length; + } + + #endregion } } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index f7dead6..f49863c 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -582,7 +582,7 @@ public static partial class AcBinarySerializer } /// - /// Optimized string writer with span-based UTF8 encoding. + /// Optimized string writer with FixStr for short strings. /// Uses stackalloc for small strings to avoid allocations. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -602,7 +602,14 @@ public static partial class AcBinarySerializer return; } - // Első előfordulás vagy nincs interning - sima string + // Try FixStr for short ASCII strings (saves 1-2 bytes per string) + if (System.Text.Ascii.IsValid(value) && BinaryTypeCode.CanEncodeAsFixStr(value.Length)) + { + context.WriteFixStr(value); + return; + } + + // Standard string encoding context.WriteByte(BinaryTypeCode.String); context.WriteStringUtf8(value); } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs index 0112bfa..7ebfd09 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs @@ -39,6 +39,8 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions public static readonly AcBinarySerializerOptions ShallowCopy = new() { MaxDepth = 0, + UseMetadata = false, + UseStringInterning = false, UseReferenceHandling = false }; @@ -160,19 +162,22 @@ internal static class BinaryTypeCode public const byte MetadataHeader = 32; // Binary has metadata section (legacy, implies HasReferenceHandling=true) public const byte NoMetadataHeader = 33; // Binary has no metadata (legacy, implies HasReferenceHandling=true) - // New flag-based header markers (48+) - // Base value 48 (0x30 = 00110000) chosen to: - // - Be distinguishable from legacy values (32, 33) - // - Not conflict with flag bits in lower nibble - // - Leave room below Int32Tiny (64) + // FixStr range: 34-65 (32 values for strings 0-31 bytes) + // FixStr encoding: FixStrBase + length (0-31) + // This saves 1 byte for short strings by combining type + length in single byte + public const byte FixStrBase = 34; // Base value for FixStr (0xA0 in MessagePack style, but we use 34) + public const byte FixStrMax = 65; // FixStrBase + 31 = maximum FixStr code + + // New flag-based header markers (48+) - moved to after FixStr range + // Note: FixStr range 34-65 overlaps with old HeaderFlagsBase, so headers use version byte prefix public const byte HeaderFlagsBase = 48; // Base value for flag-based headers (0x30) public const byte HeaderFlag_Metadata = 0x01; public const byte HeaderFlag_ReferenceHandling = 0x02; public const byte HeaderFlag_StringInternTable = 0x04; // String intern pool preloaded in header // Compact integer variants (for VarInt optimization) - public const byte Int32Tiny = 64; // -16 to 111 stored in single byte (value = code - 64 - 16) - public const byte Int32TinyMax = 191; // Upper bound for tiny int + public const byte Int32Tiny = 192; // -16 to 63 stored in single byte (value = code - 192 - 16) + public const byte Int32TinyMax = 255; // Upper bound for tiny int (192 + 64 - 1 = 255) /// /// Check if type code represents a reference (string or object). @@ -180,11 +185,35 @@ internal static class BinaryTypeCode [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsReference(byte code) => code == StringInterned || code == ObjectRef; + /// + /// Check if type code is a FixStr (short string with length encoded in type code). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFixStr(byte code) => code >= FixStrBase && code <= FixStrMax; + + /// + /// Decode FixStr length from type code. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int DecodeFixStrLength(byte code) => code - FixStrBase; + + /// + /// Encode FixStr type code for given byte length (0-31). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte EncodeFixStr(int byteLength) => (byte)(FixStrBase + byteLength); + + /// + /// Check if byte length can be encoded as FixStr. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanEncodeAsFixStr(int byteLength) => byteLength >= 0 && byteLength <= 31; + /// /// Check if type code is a tiny int (single byte int32 encoding). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTinyInt(byte code) => code >= Int32Tiny && code <= Int32TinyMax; + public static bool IsTinyInt(byte code) => code >= Int32Tiny; /// /// Decode tiny int value from type code. @@ -193,13 +222,14 @@ internal static class BinaryTypeCode public static int DecodeTinyInt(byte code) => code - Int32Tiny - 16; /// - /// Encode small int value (-16 to 111) as type code. + /// Encode small int value (-16 to 47) as type code. /// Returns true if value fits in tiny encoding. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryEncodeTinyInt(int value, out byte code) { - if (value >= -16 && value <= 111) + // Range: -16 to 47 (64 values total, fitting in 192-255) + if (value >= -16 && value <= 47) { code = (byte)(value + 16 + Int32Tiny); return true; @@ -208,7 +238,6 @@ internal static class BinaryTypeCode return false; } } - /// /// Delegate used to decide whether a property should be serialized. ///