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.
///