diff --git a/AyCode.Core/Extensions/SerializeObjectExtensions.cs b/AyCode.Core/Extensions/SerializeObjectExtensions.cs index 962875d..fd8f066 100644 --- a/AyCode.Core/Extensions/SerializeObjectExtensions.cs +++ b/AyCode.Core/Extensions/SerializeObjectExtensions.cs @@ -295,7 +295,7 @@ internal sealed class DefaultObjectPool : ObjectPool where T : class [ThreadStatic] private static T? _threadLocalItem; private readonly ConcurrentQueue _pool = new(); private readonly IPooledObjectPolicy _policy; - private const int MaxPoolSize = 16; + private const int MaxPoolSize = 8; public DefaultObjectPool(IPooledObjectPolicy policy) => _policy = policy; diff --git a/AyCode.Core/Serializers/AcSerializerOptions.cs b/AyCode.Core/Serializers/AcSerializerOptions.cs index bd3308b..745868a 100644 --- a/AyCode.Core/Serializers/AcSerializerOptions.cs +++ b/AyCode.Core/Serializers/AcSerializerOptions.cs @@ -5,6 +5,7 @@ namespace AyCode.Core.Serializers; public abstract class AcSerializerOptions { + public int MaxContextPoolSize { get; init; } = 8; public abstract AcSerializerType SerializerType { get; init; } /// @@ -27,7 +28,7 @@ public abstract class AcSerializerOptions public bool UseAsync { - get => _useAsync && ReferenceHandling != ReferenceHandlingMode.None; + get => _useAsync && (ReferenceHandling != ReferenceHandlingMode.None); //|| UseStringIntern) init => _useAsync = !DetectedIsWasm && value; } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 02f91b5..4538146 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -17,7 +17,6 @@ public static partial class AcBinarySerializer private static class BinarySerializationContextPool { private static readonly ConcurrentQueue Pool = new(); - private const int MaxPoolSize = 16; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static BinarySerializationContext Get(AcBinarySerializerOptions options) @@ -40,7 +39,7 @@ public static partial class AcBinarySerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Return(BinarySerializationContext context) { - if (Pool.Count < MaxPoolSize) + if (Pool.Count < context.Options.MaxContextPoolSize) { context.Clear(); Pool.Enqueue(context); diff --git a/AyCode.Core/Serializers/IdentityMap.cs b/AyCode.Core/Serializers/IdentityMap.cs index c077027..8d251a4 100644 --- a/AyCode.Core/Serializers/IdentityMap.cs +++ b/AyCode.Core/Serializers/IdentityMap.cs @@ -1,4 +1,7 @@ -using System.Buffers; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -45,9 +48,12 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull { private bool _useSmallInt = false; + private const int SmallBitmapSize = 64; + private const int SmallSize = 4096; + // Small int optimization (TId = int only, 0-65535) - private const int SmallBitmapSize = 1024; - private const int SmallSize = SmallBitmapSize * 64; + //private const int SmallBitmapSize = 1024; + //private const int SmallSize = SmallBitmapSize * 64; // Small int optimization (TId = int only, 0-4095) //private const int SmallSize = 4096; @@ -85,6 +91,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull { } + /// /// Tries to add a key to tracking (serialization). /// Returns true if first occurrence (key was added). @@ -141,8 +148,10 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull { var strA = Unsafe.As(ref a); var strB = Unsafe.As(ref b); + // Fast path: reference equality (interned strings) - if (ReferenceEquals(strA, strB)) return true; + //if (ReferenceEquals(strA, strB)) return true; + // Ordinal comparison is fastest for non-interned return string.Equals(strA, strB, StringComparison.Ordinal); } @@ -169,7 +178,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull InitHashTable(InitialHashCapacity); } - var bucketIdx = (hash & 0x7FFFFFFF) % _buckets!.Length; + var bucketIdx = (hash & 0x7FFFFFFF) % _bucketsLength; // Search chain for (var i = _buckets[bucketIdx]; i >= 0; i = _entries![i].Next) @@ -205,6 +214,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull if (IsInt64) return Unsafe.As(ref key).GetHashCode(); if (IsGuid) return Unsafe.As(ref key).GetHashCode(); if (IsString) return string.GetHashCode(Unsafe.As(ref key), StringComparison.Ordinal); + return key.GetHashCode(); } @@ -219,8 +229,10 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull _entries = ArrayPool.Shared.Rent(actualCapacity); _entriesLength = actualCapacity; + //Array.Clear(_entries, 0, _entriesLength); _keys = ArrayPool.Shared.Rent(actualCapacity); + //Array.Clear(_keys, 0, actualCapacity); _count = 0; } @@ -278,7 +290,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull if (_buckets == null) return false; var hash = GetHashCode(key); - var bucketIdx = (hash & 0x7FFFFFFF) % _buckets.Length; + var bucketIdx = (hash & 0x7FFFFFFF) % _bucketsLength; for (var i = _buckets[bucketIdx]; i >= 0; i = _entries![i].Next) { @@ -334,6 +346,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull if (_smallValues == null) { _smallValues = ArrayPool.Shared.Rent(SmallSize); + Array.Clear(_smallValues, 0, SmallSize); } var wordIdx = key >> 6; @@ -400,6 +413,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull if (_smallValues == null) { _smallValues = ArrayPool.Shared.Rent(SmallSize); + Array.Clear(_smallValues, 0, SmallSize); } var wordIdx = key >> 6; @@ -458,7 +472,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull } var hash = GetHashCode(key); - var bucketIdx = (hash & 0x7FFFFFFF) % _buckets.Length; + var bucketIdx = (hash & 0x7FFFFFFF) % _bucketsLength; for (var i = _buckets[bucketIdx]; i >= 0; i = _entries![i].Next) { @@ -480,17 +494,36 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull /// If true, pre-rent arrays at next capacity (useful for async Clear to shift work from hot path) public void Reset(bool preRentBuckets = false) { - if (_smallBitmap != null) - { - ArrayPool.Shared.Return(_smallBitmap); - _smallBitmap = null; - } if (_smallValues != null) { - ArrayPool.Shared.Return(_smallValues, clearArray: true); // Clear to release object refs - _smallValues = null; + //Array.Clear(_smallValues, 0, SmallSize); + + // Végigmegyünk a bitmap-en és csak a set bit-ekhez tartozó értékeket töröljük + for (var wordIdx = 0; wordIdx < SmallBitmapSize; wordIdx++) + { + var word = _smallBitmap[wordIdx]; + if (word == 0) continue; + + var baseIdx = wordIdx << 6; + while (word != 0) + { + var bitPos = BitOperations.TrailingZeroCount(word); + _smallValues[baseIdx + bitPos] = null; + word &= word - 1; // Clear lowest set bit + } + } + + //ArrayPool.Shared.Return(_smallValues, clearArray: true); // Clear to release object refs + //_smallValues = null; } - + + if (_smallBitmap != null) + { + Array.Clear(_smallBitmap, 0, SmallBitmapSize); + //ArrayPool.Shared.Return(_smallBitmap); + //_smallBitmap = null; + } + if (_buckets != null) { // Small arrays: keep and clear (faster than pool round-trip) @@ -508,8 +541,8 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull } // Large arrays: return to pool, remember half capacity - var nextCapacity = Math.Max(_bucketsLength / 2, InitialHashCapacity); - + var nextCapacity = Math.Max(_bucketsLength / 2, InitialHashCapacity * 5); + // Clear entries/keys to release object references before returning to pool // Otherwise pool holds refs → GC can't collect! if (_count > 0) @@ -517,10 +550,10 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull Array.Clear(_entries!, 0, _count); Array.Clear(_keys!, 0, _count); } - + ArrayPool.Shared.Return(_buckets); - ArrayPool.Shared.Return(_entries!); - ArrayPool.Shared.Return(_keys!); + ArrayPool.Shared.Return(_entries!,false); + ArrayPool.Shared.Return(_keys!, false); if (preRentBuckets) { @@ -531,8 +564,10 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull _entries = ArrayPool.Shared.Rent(nextCapacity); _entriesLength = nextCapacity; + //Array.Clear(_entries, 0, _entriesLength); _keys = ArrayPool.Shared.Rent(nextCapacity); + //Array.Clear(_keys, 0, nextCapacity); } else { diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs index 939a854..871cdfd 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs @@ -8,7 +8,6 @@ public static partial class AcJsonDeserializer private static class JsonDeserializationContextPool { private static readonly ConcurrentQueue Pool = new(); - private const int MaxPoolSize = 16; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static DeserializationContext Get(in AcJsonSerializerOptions options) @@ -24,7 +23,7 @@ public static partial class AcJsonDeserializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Return(DeserializationContext context, in AcJsonSerializerOptions options) { - if (Pool.Count < MaxPoolSize) + if (Pool.Count < options.MaxContextPoolSize) { context.Clear(options); Pool.Enqueue(context); diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs index 4050e22..cdc16b8 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs @@ -621,7 +621,7 @@ public static partial class AcJsonDeserializer if (_context != null) { - JsonDeserializationContextPool.Return(_context, null); + JsonDeserializationContextPool.Return(_context, _context.Options); _context = null; } _document?.Dispose(); diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs index 1d44e51..7185335 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs @@ -13,7 +13,6 @@ public static partial class AcJsonSerializer private static class SerializationContextPool { private static readonly ConcurrentQueue Pool = new(); - private const int MaxPoolSize = 16; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsonSerializationContext Get(in AcJsonSerializerOptions options) @@ -36,7 +35,7 @@ public static partial class AcJsonSerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Return(JsonSerializationContext context) { - if (Pool.Count < MaxPoolSize) + if (Pool.Count < context.Options.MaxContextPoolSize) { context.Clear(); Pool.Enqueue(context); diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs index 7c73f27..416b3ce 100644 --- a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs +++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs @@ -16,7 +16,6 @@ public static partial class AcToonSerializer private static class ToonSerializationContextPool { private static readonly ConcurrentQueue Pool = new(); - private const int MaxPoolSize = 16; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ToonSerializationContext Get(AcToonSerializerOptions options) @@ -32,7 +31,7 @@ public static partial class AcToonSerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Return(ToonSerializationContext context) { - if (Pool.Count < MaxPoolSize) + if (Pool.Count < context.Options.MaxContextPoolSize) { context.Clear(); Pool.Enqueue(context);