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.
This commit is contained in:
Loretta 2026-01-29 09:41:53 +01:00
parent f778d4faa9
commit 2eca18ca3f
5 changed files with 167 additions and 110 deletions

View File

@ -43,13 +43,18 @@ public static class Program
private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false); 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 WarmupIterations = 2000;
private static int TestIterations = 1000; private static int TestIterations = 1000;
#endif
public static void Main(string[] args) public static void Main(string[] args)
{ {
// Set console encoding to UTF-8 for proper Unicode character display // 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"; 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($"└{"".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($"GrowBufferCount: {AcBinarySerializer.GrowBufferCount}");
System.Console.WriteLine($"GrowBufferTotalBytes: {AyCode.Core.Serializers.Binaries.AcBinarySerializer.BinarySerializationContext.GrowBufferTotalBytes:N0} bytes"); System.Console.WriteLine($"GrowBufferTotalBytes: {AcBinarySerializer.GrowBufferTotalBytes:N0} bytes");
} }
// Summary: Best serializer for each category // 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($" {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 // Summary comparison
sb.AppendLine(); sb.AppendLine();
sb.AppendLine($"=== {SerializerAcBinaryDefault} vs {SerializerMessagePack} (Overall) ==="); sb.AppendLine($"=== {SerializerAcBinaryDefault} vs {SerializerMessagePack} (Overall) ===");

View File

@ -25,4 +25,8 @@
<Folder Include="Expressions\" /> <Folder Include="Expressions\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="AyCode.Core.Serializers.Console" />
</ItemGroup>
</Project> </Project>

View File

@ -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
/// <summary> /// <summary>
/// Binary serialization context. Public for generated serializers. /// Binary serialization context. Public for generated serializers.
/// </summary> /// </summary>
@ -60,6 +74,20 @@ public static partial class AcBinarySerializer
private int _position; private int _position;
private int _initialBufferSize; private int _initialBufferSize;
#if DEBUG
/// <summary>
/// Counts how many times GrowBuffer was called during serialization.
/// Used for benchmarking buffer allocation efficiency.
/// </summary>
public static int GrowBufferCount { get; set; }
/// <summary>
/// Total bytes allocated by GrowBuffer during serialization.
/// Used for benchmarking buffer allocation efficiency.
/// </summary>
public static long GrowBufferTotalBytes { get; set; }
#endif
// Use shared reference tracker from AcSerializerCommon // Use shared reference tracker from AcSerializerCommon
//private readonly AcSerializerCommon.SerializationReferenceTracker _refTracker = new(); //private readonly AcSerializerCommon.SerializationReferenceTracker _refTracker = new();
@ -132,6 +160,9 @@ public static partial class AcBinarySerializer
_initialBufferSize = Math.Max(Options.InitialBufferCapacity, MinBufferSize); _initialBufferSize = Math.Max(Options.InitialBufferCapacity, MinBufferSize);
HasPropertyFilter = Options.PropertyFilter != null; 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) if (_buffer.Length < _initialBufferSize)
{ {
ArrayPool<byte>.Shared.Return(_buffer); ArrayPool<byte>.Shared.Return(_buffer);
@ -161,6 +192,10 @@ public static partial class AcBinarySerializer
ArrayPool<byte>.Shared.Return(_propertyStateBuffer); ArrayPool<byte>.Shared.Return(_propertyStateBuffer);
_propertyStateBuffer = null; _propertyStateBuffer = null;
} }
// NOTE: GrowBufferCount <20>s GrowBufferTotalBytes nem null<6C>z<EFBFBD>dik itt,
// hogy a m<>r<EFBFBD>sek v<>g<EFBFBD>n ki tudj<64>k <20>rni az <20>rt<72>keket.
// Csak a Reset() met<65>dusban null<6C>z<EFBFBD>dnak minden <20>j fut<75>s elej<65>n.
} }
@ -409,6 +444,11 @@ public static partial class AcBinarySerializer
_buffer.AsSpan(0, _position).CopyTo(newBuffer); _buffer.AsSpan(0, _position).CopyTo(newBuffer);
ArrayPool<byte>.Shared.Return(_buffer); ArrayPool<byte>.Shared.Return(_buffer);
_buffer = newBuffer; _buffer = newBuffer;
#if DEBUG
GrowBufferCount++;
GrowBufferTotalBytes += newSize;
#endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -344,6 +344,10 @@ public static partial class AcBinarySerializer
private static BinarySerializationContext SerializeCore(object value, Type runtimeType, AcBinarySerializerOptions options) private static BinarySerializationContext SerializeCore(object value, Type runtimeType, AcBinarySerializerOptions options)
{ {
#if DEBUG
BinarySerializationContext.GrowBufferCount = 0;
BinarySerializationContext.GrowBufferTotalBytes = 0;
#endif
var context = BinarySerializationContextPool.Get(options); var context = BinarySerializationContextPool.Get(options);
context.WriteHeaderPlaceholder(); context.WriteHeaderPlaceholder();

View File

@ -1,94 +1,94 @@
using System.Collections; //using System.Collections;
using System.Runtime.CompilerServices; //using System.Runtime.CompilerServices;
using AyCode.Core.Helpers; //using AyCode.Core.Helpers;
using static AyCode.Core.Helpers.JsonUtilities; //using static AyCode.Core.Helpers.JsonUtilities;
namespace AyCode.Core.Serializers; //namespace AyCode.Core.Serializers;
/// <summary> ///// <summary>
/// Helper class for merging IId collections during deserialization. ///// Helper class for merging IId collections during deserialization.
/// Shared between JSON and Binary deserializers. ///// Shared between JSON and Binary deserializers.
/// </summary> ///// </summary>
public static class IIdCollectionMergeHelper //public static class IIdCollectionMergeHelper
{ //{
/// <summary> // /// <summary>
/// Builds a lookup dictionary from an existing IId collection. // /// Builds a lookup dictionary from an existing IId collection.
/// Maps Id values to their corresponding items. // /// Maps Id values to their corresponding items.
/// </summary> // /// </summary>
/// <param name="existingList">The existing collection to index.</param> // /// <param name="existingList">The existing collection to index.</param>
/// <param name="idGetter">Function to extract Id from an item.</param> // /// <param name="idGetter">Function to extract Id from an item.</param>
/// <param name="idType">The type of the Id property.</param> // /// <param name="idType">The type of the Id property.</param>
/// <returns>Dictionary mapping Id to item, or null if collection is empty.</returns> // /// <returns>Dictionary mapping Id to item, or null if collection is empty.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<object, object>? BuildIdLookup( // public static Dictionary<object, object>? BuildIdLookup(
IList existingList, // IList existingList,
Func<object, object?> idGetter, // Func<object, object?> idGetter,
Type idType) // Type idType)
{ // {
var count = existingList.Count; // var count = existingList.Count;
if (count == 0) return null; // if (count == 0) return null;
var dict = new Dictionary<object, object>(count); // var dict = new Dictionary<object, object>(count);
for (var i = 0; i < count; i++) // for (var i = 0; i < count; i++)
{ // {
var item = existingList[i]; // var item = existingList[i];
if (item == null) continue; // if (item == null) continue;
var id = idGetter(item); // var id = idGetter(item);
if (id != null && !IsDefaultValue(id, idType)) // if (id != null && !IsDefaultValue(id, idType))
dict[id] = item; // dict[id] = item;
} // }
return dict; // return dict;
} // }
/// <summary> // /// <summary>
/// Removes orphaned items from the collection that are not present in the source IDs. // /// Removes orphaned items from the collection that are not present in the source IDs.
/// </summary> // /// </summary>
/// <param name="existingList">The collection to clean up.</param> // /// <param name="existingList">The collection to clean up.</param>
/// <param name="existingById">Lookup dictionary of existing items.</param> // /// <param name="existingById">Lookup dictionary of existing items.</param>
/// <param name="sourceIds">Set of IDs that were seen in source data.</param> // /// <param name="sourceIds">Set of IDs that were seen in source data.</param>
public static void RemoveOrphanedItems( // public static void RemoveOrphanedItems(
IList existingList, // IList existingList,
Dictionary<object, object> existingById, // Dictionary<object, object> existingById,
HashSet<object> sourceIds) // HashSet<object> sourceIds)
{ // {
var itemsToRemove = new List<object>(); // var itemsToRemove = new List<object>();
foreach (var kvp in existingById) // foreach (var kvp in existingById)
{ // {
if (!sourceIds.Contains(kvp.Key)) // if (!sourceIds.Contains(kvp.Key))
{ // {
itemsToRemove.Add(kvp.Value); // itemsToRemove.Add(kvp.Value);
} // }
} // }
foreach (var item in itemsToRemove) // foreach (var item in itemsToRemove)
{ // {
existingList.Remove(item); // existingList.Remove(item);
} // }
} // }
/// <summary> // /// <summary>
/// Copies properties from source object to target object using metadata. // /// Copies properties from source object to target object using metadata.
/// </summary> // /// </summary>
/// <typeparam name="TPropertyInfo">Type of property info (varies by serializer).</typeparam> // /// <typeparam name="TPropertyInfo">Type of property info (varies by serializer).</typeparam>
/// <param name="source">Source object to copy from.</param> // /// <param name="source">Source object to copy from.</param>
/// <param name="target">Target object to copy to.</param> // /// <param name="target">Target object to copy to.</param>
/// <param name="properties">Array of property accessors.</param> // /// <param name="properties">Array of property accessors.</param>
/// <param name="getter">Function to get property value.</param> // /// <param name="getter">Function to get property value.</param>
/// <param name="setter">Action to set property value.</param> // /// <param name="setter">Action to set property value.</param>
public static void CopyProperties<TPropertyInfo>( // public static void CopyProperties<TPropertyInfo>(
object source, // object source,
object target, // object target,
TPropertyInfo[] properties, // TPropertyInfo[] properties,
Func<TPropertyInfo, object, object?> getter, // Func<TPropertyInfo, object, object?> getter,
Action<TPropertyInfo, object, object?> setter) // Action<TPropertyInfo, object, object?> setter)
{ // {
for (var i = 0; i < properties.Length; i++) // for (var i = 0; i < properties.Length; i++)
{ // {
var prop = properties[i]; // var prop = properties[i];
var value = getter(prop, source); // var value = getter(prop, source);
if (value != null) // if (value != null)
setter(prop, target, value); // setter(prop, target, value);
} // }
} // }
} //}