Reduce pool sizes, optimize IdentityMap, add config option
Reduced default pool sizes from 16 to 8 for serializers and object pools, now configurable via AcSerializerOptions.MaxContextPoolSize. Improved IdentityMap<TId> memory usage and cache locality by shrinking small int bitmap/array. Refactored hash table logic to use cached bucket length. Optimized Reset to clear only used entries and adjusted array pooling. String key equality now always uses ordinal comparison. Updated context pool logic to respect per-serializer pool size. Includes minor code cleanups and comments.
This commit is contained in:
parent
dbacc2da80
commit
c7f44906e7
|
|
@ -295,7 +295,7 @@ internal sealed class DefaultObjectPool<T> : ObjectPool<T> where T : class
|
|||
[ThreadStatic] private static T? _threadLocalItem;
|
||||
private readonly ConcurrentQueue<T> _pool = new();
|
||||
private readonly IPooledObjectPolicy<T> _policy;
|
||||
private const int MaxPoolSize = 16;
|
||||
private const int MaxPoolSize = 8;
|
||||
|
||||
public DefaultObjectPool(IPooledObjectPolicy<T> policy) => _policy = policy;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace AyCode.Core.Serializers;
|
|||
|
||||
public abstract class AcSerializerOptions
|
||||
{
|
||||
public int MaxContextPoolSize { get; init; } = 8;
|
||||
public abstract AcSerializerType SerializerType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ public static partial class AcBinarySerializer
|
|||
private static class BinarySerializationContextPool
|
||||
{
|
||||
private static readonly ConcurrentQueue<BinarySerializationContext> 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);
|
||||
|
|
|
|||
|
|
@ -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<TId> : 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<TId> : IIdentityMap where TId : notnull
|
|||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a key to tracking (serialization).
|
||||
/// Returns true if first occurrence (key was added).
|
||||
|
|
@ -141,8 +148,10 @@ public sealed class IdentityMap<TId> : IIdentityMap where TId : notnull
|
|||
{
|
||||
var strA = Unsafe.As<TId, string>(ref a);
|
||||
var strB = Unsafe.As<TId, string>(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<TId> : 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<TId> : IIdentityMap where TId : notnull
|
|||
if (IsInt64) return Unsafe.As<TId, long>(ref key).GetHashCode();
|
||||
if (IsGuid) return Unsafe.As<TId, Guid>(ref key).GetHashCode();
|
||||
if (IsString) return string.GetHashCode(Unsafe.As<TId, string>(ref key), StringComparison.Ordinal);
|
||||
|
||||
return key.GetHashCode();
|
||||
}
|
||||
|
||||
|
|
@ -219,8 +229,10 @@ public sealed class IdentityMap<TId> : IIdentityMap where TId : notnull
|
|||
|
||||
_entries = ArrayPool<HashSlot>.Shared.Rent(actualCapacity);
|
||||
_entriesLength = actualCapacity;
|
||||
//Array.Clear(_entries, 0, _entriesLength);
|
||||
|
||||
_keys = ArrayPool<TId>.Shared.Rent(actualCapacity);
|
||||
//Array.Clear(_keys, 0, actualCapacity);
|
||||
|
||||
_count = 0;
|
||||
}
|
||||
|
|
@ -278,7 +290,7 @@ public sealed class IdentityMap<TId> : 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<TId> : IIdentityMap where TId : notnull
|
|||
if (_smallValues == null)
|
||||
{
|
||||
_smallValues = ArrayPool<object?>.Shared.Rent(SmallSize);
|
||||
Array.Clear(_smallValues, 0, SmallSize);
|
||||
}
|
||||
|
||||
var wordIdx = key >> 6;
|
||||
|
|
@ -400,6 +413,7 @@ public sealed class IdentityMap<TId> : IIdentityMap where TId : notnull
|
|||
if (_smallValues == null)
|
||||
{
|
||||
_smallValues = ArrayPool<object?>.Shared.Rent(SmallSize);
|
||||
Array.Clear(_smallValues, 0, SmallSize);
|
||||
}
|
||||
|
||||
var wordIdx = key >> 6;
|
||||
|
|
@ -458,7 +472,7 @@ public sealed class IdentityMap<TId> : 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<TId> : IIdentityMap where TId : notnull
|
|||
/// <param name="preRentBuckets">If true, pre-rent arrays at next capacity (useful for async Clear to shift work from hot path)</param>
|
||||
public void Reset(bool preRentBuckets = false)
|
||||
{
|
||||
if (_smallBitmap != null)
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(_smallBitmap);
|
||||
_smallBitmap = null;
|
||||
}
|
||||
if (_smallValues != null)
|
||||
{
|
||||
ArrayPool<object?>.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<object?>.Shared.Return(_smallValues, clearArray: true); // Clear to release object refs
|
||||
//_smallValues = null;
|
||||
}
|
||||
|
||||
|
||||
if (_smallBitmap != null)
|
||||
{
|
||||
Array.Clear(_smallBitmap, 0, SmallBitmapSize);
|
||||
//ArrayPool<ulong>.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<TId> : 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<TId> : IIdentityMap where TId : notnull
|
|||
Array.Clear(_entries!, 0, _count);
|
||||
Array.Clear(_keys!, 0, _count);
|
||||
}
|
||||
|
||||
|
||||
ArrayPool<int>.Shared.Return(_buckets);
|
||||
ArrayPool<HashSlot>.Shared.Return(_entries!);
|
||||
ArrayPool<TId>.Shared.Return(_keys!);
|
||||
ArrayPool<HashSlot>.Shared.Return(_entries!,false);
|
||||
ArrayPool<TId>.Shared.Return(_keys!, false);
|
||||
|
||||
if (preRentBuckets)
|
||||
{
|
||||
|
|
@ -531,8 +564,10 @@ public sealed class IdentityMap<TId> : IIdentityMap where TId : notnull
|
|||
|
||||
_entries = ArrayPool<HashSlot>.Shared.Rent(nextCapacity);
|
||||
_entriesLength = nextCapacity;
|
||||
//Array.Clear(_entries, 0, _entriesLength);
|
||||
|
||||
_keys = ArrayPool<TId>.Shared.Rent(nextCapacity);
|
||||
//Array.Clear(_keys, 0, nextCapacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ public static partial class AcJsonDeserializer
|
|||
private static class JsonDeserializationContextPool
|
||||
{
|
||||
private static readonly ConcurrentQueue<DeserializationContext> 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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ public static partial class AcJsonSerializer
|
|||
private static class SerializationContextPool
|
||||
{
|
||||
private static readonly ConcurrentQueue<JsonSerializationContext> 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);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ public static partial class AcToonSerializer
|
|||
private static class ToonSerializationContextPool
|
||||
{
|
||||
private static readonly ConcurrentQueue<ToonSerializationContext> 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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue