diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs index 69313b9..f224a6d 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs @@ -319,6 +319,20 @@ 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. + if (length <= 128 && System.Text.Ascii.IsValid(_buffer.AsSpan(_position, length))) + { + var pos = _position; + _position += length; + return string.Create(length, (Buffer: _buffer, Start: pos), static (chars, state) => + { + var src = state.Buffer.AsSpan(state.Start, chars.Length); + for (int i = 0; i < chars.Length; i++) + chars[i] = (char)src[i]; + }); + } + var value = Utf8NoBom.GetString(_buffer, _position, length); _position += length; return value; diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs index f291741..f8046d5 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs @@ -83,6 +83,7 @@ public static partial class AcBinaryDeserializer /// Wire format: All properties are written WITH type markers (including Id for IId types). /// No hashcode prefix - position-based footer handles reference tracking. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void PopulateObjectCore( BinaryDeserializationContext context, object target, @@ -336,6 +337,12 @@ public static partial class AcBinaryDeserializer case PropertyAccessorType.UInt64: propInfo.SetUInt64(target, context.ReadVarULong()); return; + case PropertyAccessorType.Boolean: + propInfo.SetBoolean(target, context.ReadByte() != 0); + return; + case PropertyAccessorType.Enum: + propInfo.SetEnumAsInt32(target, context.ReadVarInt()); + return; } } @@ -410,29 +417,30 @@ public static partial class AcBinaryDeserializer for (int i = 0; i < count; i++) { - var peekCode = context.PeekByte(); + // Read marker once — eliminates redundant PeekByte + ReadByte boundary checks + var typeCode = context.ReadByte(); // If we have an existing item at this index and the incoming is an object, reuse it - if (i < existingCount && peekCode == BinaryTypeCode.Object && elementMetadata != null) + if (i < existingCount && typeCode == BinaryTypeCode.Object && elementMetadata != null) { var existingItem = existingList[i]; if (existingItem != null) { - context.ReadByte(); // consume Object marker - PopulateObjectCore(context, existingItem, wrapper, nextDepth, skipDefaultWrite: false); + PopulateObjectPropertiesIndexed(context, existingItem, wrapper, nextDepth, skipDefaultWrite: false); continue; } } // Read new value — use pre-resolved wrapper for Object elements to skip GetWrapper dictionary lookup object? value; - if (peekCode == BinaryTypeCode.Object && elementMetadata != null) + if (typeCode == BinaryTypeCode.Object && elementMetadata != null) { - context.ReadByte(); // consume Object marker value = ReadObjectCoreWithWrapper(context, wrapper, nextDepth, cacheIndex: -1); } else { + // Marker already consumed → rewind so ReadValue can read it + context._position--; value = ReadValue(context, elementType, nextDepth); } @@ -508,21 +516,22 @@ public static partial class AcBinaryDeserializer for (int i = 0; i < arrayCount; i++) { - var itemCode = context.PeekByte(); + // Read marker once — eliminates redundant PeekByte + ReadByte boundary checks + var itemCode = context.ReadByte(); // Read or create the new item object? newItem; if (itemCode == BinaryTypeCode.Object) { // Fast path: use pre-resolved wrapper, skip GetWrapper dictionary lookup - context.ReadByte(); // consume Object marker newItem = CreateInstance(elementType, elementMetadata); if (newItem == null) continue; - PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true); + PopulateObjectPropertiesIndexed(context, newItem, wrapper, nextDepth, skipDefaultWrite: true); } else { - // Fallback for non-Object markers (null, ObjectRef, etc.) + // Marker already consumed → rewind so ReadValue can read it + context._position--; newItem = ReadValue(context, elementType, nextDepth); if (newItem == null) continue; } @@ -616,21 +625,22 @@ public static partial class AcBinaryDeserializer for (int i = 0; i < arrayCount; i++) { - var itemCode = context.PeekByte(); + // Read marker once — eliminates redundant PeekByte + ReadByte boundary checks + var itemCode = context.ReadByte(); // Read or create the new item object? newItem; if (itemCode == BinaryTypeCode.Object) { // Fast path: use pre-resolved wrapper, skip GetWrapper dictionary lookup - context.ReadByte(); // consume Object marker newItem = CreateInstance(elementType, elementMetadata); if (newItem == null) continue; - PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true); + PopulateObjectPropertiesIndexed(context, newItem, wrapper, nextDepth, skipDefaultWrite: true); } else { - // Fallback for non-Object markers (null, ObjectRef, etc.) + // Marker already consumed → rewind so ReadValue can read it + context._position--; newItem = ReadValue(context, elementType, nextDepth); if (newItem == null) continue; } @@ -640,7 +650,7 @@ public static partial class AcBinaryDeserializer { // Track this ID as seen in source sourceIds?.Add(itemId); - + if (existingById != null && existingById.TryGetValue(itemId, out var existingItem)) { // Copy properties to existing item (preserves reference) diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index 1429b62..c5f63ec 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -1536,6 +1536,12 @@ public static partial class AcBinarySerializer case PropertyAccessorType.UInt64: context.WriteVarULong(prop.GetUInt64(obj)); return; + case PropertyAccessorType.Boolean: + context.WriteByte(prop.GetBoolean(obj) ? (byte)1 : (byte)0); + return; + case PropertyAccessorType.Enum: + context.WriteVarInt(prop.GetEnumAsInt32(obj)); + return; } } diff --git a/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs b/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs index ab4be0b..f391899 100644 --- a/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs +++ b/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs @@ -94,7 +94,7 @@ public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase /// /// Maps AccessorType to the BinaryTypeCode that would normally be written as marker. - /// Returns null for types that always need a stream marker (bool, enum, string, object/nullable). + /// Returns null for types that always need a stream marker (string, object/nullable). /// private static byte? ComputeExpectedTypeCode(PropertyAccessorType accessorType) => accessorType switch { @@ -110,6 +110,8 @@ public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase PropertyAccessorType.UInt16 => BinaryTypeCode.UInt16, PropertyAccessorType.UInt32 => BinaryTypeCode.UInt32, PropertyAccessorType.UInt64 => BinaryTypeCode.UInt64, - _ => null // Bool, Enum, String, Object — always read marker from stream + PropertyAccessorType.Boolean => BinaryTypeCode.True, + PropertyAccessorType.Enum => BinaryTypeCode.Enum, + _ => null // String, Object — always write marker to stream }; } diff --git a/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs b/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs index 415f855..c2dcb23 100644 --- a/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs +++ b/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs @@ -102,7 +102,7 @@ public abstract class BinaryPropertySetterBase : PropertySetterBase /// /// Maps AccessorType to the BinaryTypeCode that would normally be read as marker. - /// Returns null for types that always need a stream marker (bool, enum, string, object/nullable). + /// Returns null for types that always need a stream marker (string, object/nullable). /// private static byte? ComputeExpectedTypeCode(PropertyAccessorType accessorType) => accessorType switch { @@ -118,6 +118,8 @@ public abstract class BinaryPropertySetterBase : PropertySetterBase PropertyAccessorType.UInt16 => BinaryTypeCode.UInt16, PropertyAccessorType.UInt32 => BinaryTypeCode.UInt32, PropertyAccessorType.UInt64 => BinaryTypeCode.UInt64, - _ => null // Bool, Enum, String, Object — always read marker from stream + PropertyAccessorType.Boolean => BinaryTypeCode.True, + PropertyAccessorType.Enum => BinaryTypeCode.Enum, + _ => null // String, Object — always read marker from stream }; }