From 2eca18ca3f5bbe3a054699804f1d5a3d5daebb3f Mon Sep 17 00:00:00 2001 From: Loretta Date: Thu, 29 Jan 2026 09:41:53 +0100 Subject: [PATCH] Track buffer growth stats in DEBUG; disable IId merge helper Add DEBUG-only tracking of buffer growth in AcBinarySerializer for benchmarking, with stats output in console app. Expose stats via static properties and reset at serialization start. Add InternalsVisibleTo for console access. Comment out IIdCollectionMergeHelper.cs. Minor code cleanups included. --- AyCode.Core.Serializers.Console/Program.cs | 15 +- AyCode.Core/AyCode.Core.csproj | 4 + ...rySerializer.BinarySerializationContext.cs | 80 ++++++-- .../Binaries/AcBinarySerializer.cs | 4 + .../Serializers/IIdCollectionMergeHelper.cs | 174 +++++++++--------- 5 files changed, 167 insertions(+), 110 deletions(-) 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); +// } +// } +//}