diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs index 91f308b..27acf85 100644 --- a/AyCode.Core.Serializers.Console/Program.cs +++ b/AyCode.Core.Serializers.Console/Program.cs @@ -43,13 +43,18 @@ public static class Program private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false); + #if DEBUG + private static int WarmupIterations = 5; + private static int TestIterations = 10; + #else private static int WarmupIterations = 2000; private static int TestIterations = 1000; + #endif public static void Main(string[] args) { // Set console encoding to UTF-8 for proper Unicode character display - System.Console.OutputEncoding = System.Text.Encoding.UTF8; + System.Console.OutputEncoding = Encoding.UTF8; var mode = args.Length > 0 ? args[0].ToLower() : "all"; @@ -779,8 +784,8 @@ public static class Program } System.Console.WriteLine($"└{"─".PadRight(6, '─')}─{"─".PadRight(27, '─')}┴{"─".PadRight(12, '─')}┴{"─".PadRight(14, '─')}┴{"─".PadRight(14, '─')}┴{"─".PadRight(13, '─')}┘"); - System.Console.WriteLine($"GrowBufferCount: {AyCode.Core.Serializers.Binaries.AcBinarySerializer.BinarySerializationContext.GrowBufferCount}"); - System.Console.WriteLine($"GrowBufferTotalBytes: {AyCode.Core.Serializers.Binaries.AcBinarySerializer.BinarySerializationContext.GrowBufferTotalBytes:N0} bytes"); + System.Console.WriteLine($"GrowBufferCount: {AcBinarySerializer.GrowBufferCount}"); + System.Console.WriteLine($"GrowBufferTotalBytes: {AcBinarySerializer.GrowBufferTotalBytes:N0} bytes"); } // Summary: Best serializer for each category @@ -979,8 +984,12 @@ public static class Program sb.AppendLine($" {SerializerAcBinaryDefault} vs {SerializerMessagePack}: Size {sizePct:+0;-0}% │ Ser {serPct:+0;-0}% │ Des {desPct:+0;-0}% │ RT {rtPct:+0;-0}%"); } + + sb.AppendLine($"GrowBufferCount: {AcBinarySerializer.GrowBufferCount}"); + sb.AppendLine($"GrowBufferTotalBytes: {AcBinarySerializer.GrowBufferTotalBytes:N0} bytes"); } + // Summary comparison sb.AppendLine(); sb.AppendLine($"=== {SerializerAcBinaryDefault} vs {SerializerMessagePack} (Overall) ==="); diff --git a/AyCode.Core/AyCode.Core.csproj b/AyCode.Core/AyCode.Core.csproj index 4983021..8cbe66a 100644 --- a/AyCode.Core/AyCode.Core.csproj +++ b/AyCode.Core/AyCode.Core.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index c85c47f..bc364b2 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -45,6 +45,20 @@ public static partial class AcBinarySerializer } } + public static int GrowBufferCount => +#if DEBUG + BinarySerializationContext.GrowBufferCount; +#else + -1; +#endif + + public static long GrowBufferTotalBytes => +#if DEBUG + BinarySerializationContext.GrowBufferTotalBytes; +#else + -1; +#endif + /// /// Binary serialization context. Public for generated serializers. /// @@ -60,6 +74,20 @@ public static partial class AcBinarySerializer private int _position; private int _initialBufferSize; +#if DEBUG + /// + /// Counts how many times GrowBuffer was called during serialization. + /// Used for benchmarking buffer allocation efficiency. + /// + public static int GrowBufferCount { get; set; } + + /// + /// Total bytes allocated by GrowBuffer during serialization. + /// Used for benchmarking buffer allocation efficiency. + /// + public static long GrowBufferTotalBytes { get; set; } +#endif + // Use shared reference tracker from AcSerializerCommon //private readonly AcSerializerCommon.SerializationReferenceTracker _refTracker = new(); @@ -102,7 +130,7 @@ public static partial class AcBinarySerializer public byte MinStringInternLength => Options.MinStringInternLength; public byte MaxStringInternLength => Options.MaxStringInternLength; public BinaryPropertyFilter? PropertyFilter => Options.PropertyFilter; - + /// /// Cached check for PropertyFilter != null. Set in Reset() to avoid property getter in hot loop. /// @@ -120,7 +148,7 @@ public static partial class AcBinarySerializer /// /// Factory for creating BinarySerializeTypeMetadata instances. /// - protected override Func MetadataFactory + protected override Func MetadataFactory => static t => new BinarySerializeTypeMetadata(t, HasJsonIgnoreAttribute); public override void Reset(AcBinarySerializerOptions options) @@ -132,6 +160,9 @@ public static partial class AcBinarySerializer _initialBufferSize = Math.Max(Options.InitialBufferCapacity, MinBufferSize); HasPropertyFilter = Options.PropertyFilter != null; + // NOTE: GrowBufferCount és GrowBufferTotalBytes NEM nullázódik itt! + // Kumulatívan gyűjtjük a benchmark során. + if (_buffer.Length < _initialBufferSize) { ArrayPool.Shared.Return(_buffer); @@ -161,6 +192,10 @@ public static partial class AcBinarySerializer ArrayPool.Shared.Return(_propertyStateBuffer); _propertyStateBuffer = null; } + + // NOTE: GrowBufferCount �s GrowBufferTotalBytes nem null�z�dik itt, + // hogy a m�r�sek v�g�n ki tudj�k �rni az �rt�keket. + // Csak a Reset() met�dusban null�z�dnak minden �j fut�s elej�n. } @@ -183,7 +218,7 @@ public static partial class AcBinarySerializer ArrayPool.Shared.Return(_propertyStateBuffer); _propertyStateBuffer = null; } - + } #region String Interning @@ -409,6 +444,11 @@ public static partial class AcBinarySerializer _buffer.AsSpan(0, _position).CopyTo(newBuffer); ArrayPool.Shared.Return(_buffer); _buffer = newBuffer; + + #if DEBUG + GrowBufferCount++; + GrowBufferTotalBytes += newSize; + #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -516,7 +556,7 @@ public static partial class AcBinarySerializer _buffer[position] = (byte)value; return; } - + // Multi-byte case - need to shift buffer if new encoding is longer // For simplicity, we'll rewrite from the position // This is rare for property counts @@ -535,10 +575,10 @@ public static partial class AcBinarySerializer pos++; } currentSize++; // Include final byte without continuation bit - + // Calculate new size needed var newSize = GetVarUIntSize(value); - + if (newSize == currentSize) { // Same size - just overwrite @@ -556,7 +596,7 @@ public static partial class AcBinarySerializer var delta = currentSize - newSize; Array.Copy(_buffer, position + currentSize, _buffer, position + newSize, _position - position - currentSize); _position -= delta; - + var tempPos = position; while (value >= 0x80) { @@ -572,7 +612,7 @@ public static partial class AcBinarySerializer EnsureCapacity(delta); Array.Copy(_buffer, position + currentSize, _buffer, position + newSize, _position - position - currentSize); _position += delta; - + var tempPos = position; while (value >= 0x80) { @@ -687,7 +727,7 @@ public static partial class AcBinarySerializer _position += value.Length; return; } - + // Standard path for multi-byte UTF8 var byteCount = Utf8NoBom.GetByteCount(value); WriteVarUInt((uint)byteCount); @@ -821,7 +861,7 @@ public static partial class AcBinarySerializer { EnsureCapacity(source.Length); var destination = _buffer.AsSpan(_position, source.Length); - + if (Vector.IsHardwareAccelerated && source.Length >= Vector.Count * 2) { CopyWithSimd(source, destination); @@ -830,7 +870,7 @@ public static partial class AcBinarySerializer { source.CopyTo(destination); } - + _position += source.Length; } @@ -843,7 +883,7 @@ public static partial class AcBinarySerializer 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++) @@ -852,7 +892,7 @@ public static partial class AcBinarySerializer vec.CopyTo(destination.Slice(i, vectorSize)); i += vectorSize; } - + // Copy remaining bytes if (i < length) { @@ -890,7 +930,7 @@ public static partial class AcBinarySerializer // 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)); @@ -903,7 +943,7 @@ public static partial class AcBinarySerializer #region Header and Metadata private int _headerPosition; - + // Footer-based string interning: no estimation or shifting needed // Header: [version][flags][footerPosition (4 bytes, only if string interning)] // Body: data with StringInterned indices @@ -916,7 +956,7 @@ public static partial class AcBinarySerializer public int EstimateHeaderPayloadSize() { var size = 0; - + // Only property names are in header now if (UseMetadata && _propertyNameList is { Count: > 0 }) { @@ -928,7 +968,7 @@ public static partial class AcBinarySerializer size += GetVarUIntSize((uint)byteCount) + byteCount; } } - + return size; } @@ -1075,7 +1115,7 @@ public static partial class AcBinarySerializer //[MethodImpl(MethodImplOptions.AggressiveInlining)] //public bool TrackForScanning(object obj) => _refTracker.TrackForScanning(obj); - + /// /// IId-aware tracking for the scan phase. /// First checks IId match (different instance, same Id), then falls back to ReferenceEquals. @@ -1162,12 +1202,12 @@ public static partial class AcBinarySerializer { var length = value.Length; EnsureCapacity(1 + length); - + // Ascii.FromUtf16: SIMD-optimized ASCII conversion // Returns actual bytes written - if less than input length, there was a non-ASCII char var destSpan = _buffer.AsSpan(_position + 1, length); var status = Ascii.FromUtf16(value.AsSpan(), destSpan, out var bytesWritten); - + if (status == System.Buffers.OperationStatus.Done && bytesWritten == length) { // Success - write FixStr header diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index c594a3a..87a9f36 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -344,6 +344,10 @@ public static partial class AcBinarySerializer private static BinarySerializationContext SerializeCore(object value, Type runtimeType, AcBinarySerializerOptions options) { +#if DEBUG + BinarySerializationContext.GrowBufferCount = 0; + BinarySerializationContext.GrowBufferTotalBytes = 0; +#endif var context = BinarySerializationContextPool.Get(options); context.WriteHeaderPlaceholder(); diff --git a/AyCode.Core/Serializers/IIdCollectionMergeHelper.cs b/AyCode.Core/Serializers/IIdCollectionMergeHelper.cs index d4bd3cc..1af42ae 100644 --- a/AyCode.Core/Serializers/IIdCollectionMergeHelper.cs +++ b/AyCode.Core/Serializers/IIdCollectionMergeHelper.cs @@ -1,94 +1,94 @@ -using System.Collections; -using System.Runtime.CompilerServices; -using AyCode.Core.Helpers; -using static AyCode.Core.Helpers.JsonUtilities; +//using System.Collections; +//using System.Runtime.CompilerServices; +//using AyCode.Core.Helpers; +//using static AyCode.Core.Helpers.JsonUtilities; -namespace AyCode.Core.Serializers; +//namespace AyCode.Core.Serializers; -/// -/// Helper class for merging IId collections during deserialization. -/// Shared between JSON and Binary deserializers. -/// -public static class IIdCollectionMergeHelper -{ - /// - /// Builds a lookup dictionary from an existing IId collection. - /// Maps Id values to their corresponding items. - /// - /// The existing collection to index. - /// Function to extract Id from an item. - /// The type of the Id property. - /// Dictionary mapping Id to item, or null if collection is empty. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary? BuildIdLookup( - IList existingList, - Func idGetter, - Type idType) - { - var count = existingList.Count; - if (count == 0) return null; +///// +///// Helper class for merging IId collections during deserialization. +///// Shared between JSON and Binary deserializers. +///// +//public static class IIdCollectionMergeHelper +//{ +// /// +// /// Builds a lookup dictionary from an existing IId collection. +// /// Maps Id values to their corresponding items. +// /// +// /// The existing collection to index. +// /// Function to extract Id from an item. +// /// The type of the Id property. +// /// Dictionary mapping Id to item, or null if collection is empty. +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public static Dictionary? BuildIdLookup( +// IList existingList, +// Func idGetter, +// Type idType) +// { +// var count = existingList.Count; +// if (count == 0) return null; - var dict = new Dictionary(count); - for (var i = 0; i < count; i++) - { - var item = existingList[i]; - if (item == null) continue; +// var dict = new Dictionary(count); +// for (var i = 0; i < count; i++) +// { +// var item = existingList[i]; +// if (item == null) continue; - var id = idGetter(item); - if (id != null && !IsDefaultValue(id, idType)) - dict[id] = item; - } - return dict; - } +// var id = idGetter(item); +// if (id != null && !IsDefaultValue(id, idType)) +// dict[id] = item; +// } +// return dict; +// } - /// - /// Removes orphaned items from the collection that are not present in the source IDs. - /// - /// The collection to clean up. - /// Lookup dictionary of existing items. - /// Set of IDs that were seen in source data. - public static void RemoveOrphanedItems( - IList existingList, - Dictionary existingById, - HashSet sourceIds) - { - var itemsToRemove = new List(); - foreach (var kvp in existingById) - { - if (!sourceIds.Contains(kvp.Key)) - { - itemsToRemove.Add(kvp.Value); - } - } +// /// +// /// Removes orphaned items from the collection that are not present in the source IDs. +// /// +// /// The collection to clean up. +// /// Lookup dictionary of existing items. +// /// Set of IDs that were seen in source data. +// public static void RemoveOrphanedItems( +// IList existingList, +// Dictionary existingById, +// HashSet sourceIds) +// { +// var itemsToRemove = new List(); +// foreach (var kvp in existingById) +// { +// if (!sourceIds.Contains(kvp.Key)) +// { +// itemsToRemove.Add(kvp.Value); +// } +// } - foreach (var item in itemsToRemove) - { - existingList.Remove(item); - } - } +// foreach (var item in itemsToRemove) +// { +// existingList.Remove(item); +// } +// } - /// - /// Copies properties from source object to target object using metadata. - /// - /// Type of property info (varies by serializer). - /// Source object to copy from. - /// Target object to copy to. - /// Array of property accessors. - /// Function to get property value. - /// Action to set property value. - public static void CopyProperties( - object source, - object target, - TPropertyInfo[] properties, - Func getter, - Action setter) - { - for (var i = 0; i < properties.Length; i++) - { - var prop = properties[i]; - var value = getter(prop, source); - if (value != null) - setter(prop, target, value); - } - } -} +// /// +// /// Copies properties from source object to target object using metadata. +// /// +// /// Type of property info (varies by serializer). +// /// Source object to copy from. +// /// Target object to copy to. +// /// Array of property accessors. +// /// Function to get property value. +// /// Action to set property value. +// public static void CopyProperties( +// object source, +// object target, +// TPropertyInfo[] properties, +// Func getter, +// Action setter) +// { +// for (var i = 0; i < properties.Length; i++) +// { +// var prop = properties[i]; +// var value = getter(prop, source); +// if (value != null) +// setter(prop, target, value); +// } +// } +//}