Pool arrays for dup data and intern cache in deserializer
Reduce allocations by pooling int[] and object?[] arrays used for duplicate data and interned references during binary deserialization. Arrays are now rented and reused via ArrayPool<T>, with logic to clear or return them as appropriate. This improves performance and reduces GC pressure in steady-state scenarios.
This commit is contained in:
parent
9b151fd6cf
commit
bc62488965
|
|
@ -172,6 +172,7 @@ public static partial class AcBinaryDeserializer
|
|||
/// Reads intern footer: [dupCount][pos0][idx0][pos1][idx1]...
|
||||
/// Shared for string interning AND IId object references.
|
||||
/// VarUInt format read into flat int[] for fast hot path access.
|
||||
/// Arrays are pooled via ContextClass for zero steady-state allocation.
|
||||
/// </summary>
|
||||
private void ReadFooterIndices(int footerPosition)
|
||||
{
|
||||
|
|
@ -191,16 +192,17 @@ public static partial class AcBinaryDeserializer
|
|||
}
|
||||
else
|
||||
{
|
||||
// Read VarUInt pairs into flat int[]
|
||||
// Read VarUInt pairs into pooled flat int[]
|
||||
var intCount = dupCount * 2;
|
||||
_dupData = new int[intCount];
|
||||
_dupData = ContextClass.RentDupData(intCount);
|
||||
for (var i = 0; i < dupCount; i++)
|
||||
{
|
||||
_dupData[i * 2] = (int)ReadVarUInt(); // position
|
||||
_dupData[i * 2 + 1] = (int)ReadVarUInt(); // cacheIndex
|
||||
}
|
||||
|
||||
_internCache = new object?[dupCount];
|
||||
_internCache = ContextClass.RentInternCache(dupCount);
|
||||
ContextClass.SetInternCacheUsed(dupCount);
|
||||
// Cache first dup position for ultra-fast hot path
|
||||
_nextDupPosition = _dupData[0];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace AyCode.Core.Serializers.Binaries;
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ public static partial class AcBinaryDeserializer
|
|||
/// Heap-allocated context class for binary deserialization.
|
||||
/// Inherits from AcSerializerContextBase for unified metadata caching and IId-based reference tracking.
|
||||
/// Used in composition with the ref struct BinaryDeserializationContext.
|
||||
/// Holds pooled arrays for intern cache reuse across deserializations.
|
||||
/// </summary>
|
||||
internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
|
||||
{
|
||||
|
|
@ -17,10 +19,102 @@ public static partial class AcBinaryDeserializer
|
|||
protected override Func<Type, BinaryDeserializeTypeMetadata> MetadataFactory
|
||||
=> static t => new BinaryDeserializeTypeMetadata(t);
|
||||
|
||||
// Pooled arrays - reused across deserializations, zero steady-state allocation
|
||||
private int[]? _pooledDupData;
|
||||
private object?[]? _pooledInternCache;
|
||||
private int _pooledDupDataLength;
|
||||
private int _pooledInternCacheLength;
|
||||
private int _lastInternCacheUsed; // how many slots were used (for targeted Clear)
|
||||
|
||||
// Small arrays: keep across calls. Large arrays: return to pool in Clear().
|
||||
private const int SmallArrayThreshold = 256;
|
||||
|
||||
public BinaryDeserializationContextClass()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or allocates a pooled int[] for dup data (position/cacheIndex pairs).
|
||||
/// </summary>
|
||||
public int[] RentDupData(int minLength)
|
||||
{
|
||||
if (_pooledDupData != null && _pooledDupDataLength >= minLength)
|
||||
return _pooledDupData;
|
||||
|
||||
// Too small - return old and rent bigger
|
||||
if (_pooledDupData != null)
|
||||
ArrayPool<int>.Shared.Return(_pooledDupData);
|
||||
|
||||
_pooledDupData = ArrayPool<int>.Shared.Rent(minLength);
|
||||
_pooledDupDataLength = _pooledDupData.Length;
|
||||
return _pooledDupData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or allocates a pooled object?[] for intern cache.
|
||||
/// </summary>
|
||||
public object?[] RentInternCache(int minLength)
|
||||
{
|
||||
if (_pooledInternCache != null && _pooledInternCacheLength >= minLength)
|
||||
{
|
||||
// Reuse - clear previous content (release GC roots)
|
||||
if (_lastInternCacheUsed > 0)
|
||||
{
|
||||
Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
|
||||
_lastInternCacheUsed = 0;
|
||||
}
|
||||
return _pooledInternCache;
|
||||
}
|
||||
|
||||
// Too small - return old and rent bigger
|
||||
if (_pooledInternCache != null)
|
||||
ArrayPool<object?>.Shared.Return(_pooledInternCache, clearArray: true);
|
||||
|
||||
_pooledInternCache = ArrayPool<object?>.Shared.Rent(minLength);
|
||||
_pooledInternCacheLength = _pooledInternCache.Length;
|
||||
_lastInternCacheUsed = 0;
|
||||
return _pooledInternCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks how many intern cache slots were used (for targeted Clear).
|
||||
/// </summary>
|
||||
public void SetInternCacheUsed(int count)
|
||||
{
|
||||
_lastInternCacheUsed = count;
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
|
||||
// Intern cache: clear GC roots, return large arrays to pool
|
||||
if (_pooledInternCache != null)
|
||||
{
|
||||
if (_pooledInternCacheLength > SmallArrayThreshold)
|
||||
{
|
||||
// Large: return to pool - no pre-rent needed, ReadFooterIndices will rent on demand
|
||||
ArrayPool<object?>.Shared.Return(_pooledInternCache, clearArray: true);
|
||||
_pooledInternCache = null;
|
||||
_pooledInternCacheLength = 0;
|
||||
}
|
||||
else if (_lastInternCacheUsed > 0)
|
||||
{
|
||||
// Small: keep array, just clear content (release GC roots)
|
||||
Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
|
||||
}
|
||||
_lastInternCacheUsed = 0;
|
||||
}
|
||||
|
||||
// Dup data: no GC roots (int[]), return large arrays to pool
|
||||
if (_pooledDupData != null && _pooledDupDataLength > SmallArrayThreshold)
|
||||
{
|
||||
ArrayPool<int>.Shared.Return(_pooledDupData);
|
||||
_pooledDupData = null;
|
||||
_pooledDupDataLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset(AcBinarySerializerOptions options)
|
||||
{
|
||||
Clear();
|
||||
|
|
|
|||
Loading…
Reference in New Issue