diff --git a/AyCode.Core/Serializers/IdentityMap.cs b/AyCode.Core/Serializers/IdentityMap.cs
index 68256cf..5b6eddb 100644
--- a/AyCode.Core/Serializers/IdentityMap.cs
+++ b/AyCode.Core/Serializers/IdentityMap.cs
@@ -43,35 +43,35 @@ public interface IIdentityMap
/// - Large keys: custom hash table with chaining
/// No Dictionary overhead, no per-entry allocation.
///
-/// The ID type (int, long, Guid, string)
-public sealed class IdentityMap : IIdentityMap where TId : notnull
+/// The key type (int, long, Guid, string)
+/// The value type
+public sealed class IdentityMap : IIdentityMap where TKey : notnull
{
- private const bool _useSmallInt = true;
+ private const bool _useSmallInt = false;
- // Small int optimization (TId = int only, 0-4095), 512 bytes (fits in L1 cache!)
+ // Small int optimization (TKey = int only, 0-4095), 512 bytes (fits in L1 cache!)
private const int SmallBitmapSize = 64;
private const int SmallSize = 4096;
- // Small int optimization (TId = int only, 0-65535)
+ // Small int optimization (TKey = int only, 0-65535)
//private const int SmallBitmapSize = 1024;
//private const int SmallSize = SmallBitmapSize * 64;
// Slot for hash table entries (generation needed for hash table validity)
private struct HashSlot
{
- public object? Value;
+ public TValue Value;
public int Next; // next slot index in chain (-1 = end)
}
- // Small int storage - SIMPLE: bitmap for "seen?" + direct object[] for values
+ // Small int storage - SIMPLE: bitmap for "seen?" + direct TValue[] for values
// Bitmap protects values - no generation needed, no value array clearing on Reset!
private ulong[]? _smallBitmap; // Cache-friendly "seen?" check (512 bytes, cleared on Reset)
- private object?[]? _smallValues; // Direct value storage (NOT cleared on Reset - bitmap protects)
// Hash table storage (for large ints and other types)
private int[]? _buckets; // bucket index → first entry index
private HashSlot[]? _entries; // hash table entries
- private TId[]? _keys; // keys for equality check
+ private TKey[]? _keys; // keys for equality check
private int _count; // number of entries in hash table
private int _bucketsLength; // actual rented length (for modulo)
private int _entriesLength; // actual capacity
@@ -79,27 +79,28 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
private const int InitialHashCapacity = 16;
// Type checks (JIT eliminates these at compile time)
- private static readonly bool IsInt32 = typeof(TId) == typeof(int);
- private static readonly bool IsInt64 = typeof(TId) == typeof(long);
- private static readonly bool IsGuid = typeof(TId) == typeof(Guid);
- private static readonly bool IsString = typeof(TId) == typeof(string);
+ private static readonly bool IsInt32 = typeof(TKey) == typeof(int);
+ private static readonly bool IsInt64 = typeof(TKey) == typeof(long);
+ private static readonly bool IsGuid = typeof(TKey) == typeof(Guid);
+ private static readonly bool IsString = typeof(TKey) == typeof(string);
+ private static readonly bool IsValueTypeValue = typeof(TValue).IsValueType;
public IdentityMap()
{
}
-
+
///
/// Tries to add a key to tracking (serialization).
/// Returns true if first occurrence (key was added).
/// Returns false if already seen.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool TryAddKey(TId key)
+ public bool TryAddKey(TKey key)
{
// Small int fast path
if (_useSmallInt && IsInt32)
{
- var intKey = Unsafe.As(ref key);
+ var intKey = Unsafe.As(ref key);
if ((uint)intKey < SmallSize)
{
return TryAddSmallInt(intKey);
@@ -113,12 +114,11 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryAddSmallInt(int key)
{
- // Lazy init - ONLY bitmap for tracking (serializer doesn't need _smallValues)
+ // Lazy init - ONLY bitmap for tracking
if (_smallBitmap == null)
{
_smallBitmap = ArrayPool.Shared.Rent(SmallBitmapSize);
Array.Clear(_smallBitmap, 0, SmallBitmapSize);
- // _smallValues allocated lazily in TryGetOrAddSmallIntValue when needed
}
var wordIdx = key >> 6;
@@ -133,17 +133,17 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool KeyEquals(TId a, TId b)
+ private static bool KeyEquals(TKey a, TKey b)
{
- // JIT eliminates these branches at compile time for each TId instantiation
+ // JIT eliminates these branches at compile time for each TKey instantiation
if (IsInt32)
{
- return Unsafe.As(ref a) == Unsafe.As(ref b);
+ return Unsafe.As(ref a) == Unsafe.As(ref b);
}
if (IsString)
{
- var strA = Unsafe.As(ref a);
- var strB = Unsafe.As(ref b);
+ var strA = Unsafe.As(ref a);
+ var strB = Unsafe.As(ref b);
// Fast path: reference equality (interned strings)
//if (ReferenceEquals(strA, strB)) return true;
@@ -153,18 +153,18 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
}
if (IsInt64)
{
- return Unsafe.As(ref a) == Unsafe.As(ref b);
+ return Unsafe.As(ref a) == Unsafe.As(ref b);
}
if (IsGuid)
{
- return Unsafe.As(ref a) == Unsafe.As(ref b);
+ return Unsafe.As(ref a) == Unsafe.As(ref b);
}
// Fallback for other types
- return EqualityComparer.Default.Equals(a, b);
+ return EqualityComparer.Default.Equals(a, b);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryAddHash(TId key, out int slotIndex)
+ private bool TryAddHash(TKey key, out int slotIndex)
{
var hash = GetHashCode(key);
@@ -179,8 +179,8 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
// Search chain
for (var i = _buckets[bucketIdx]; i >= 0; i = _entries![i].Next)
{
- //if (EqualityComparer.Default.Equals(_keys![i], key))
- if (KeyEquals(_keys![i], key)) // Direct comparison, no virtual call
+ //if (EqualityComparer.Default.Equals(_keys![i], key))
+ if (KeyEquals(_keys![i], key)) // Direct comparison, no virtual call
{
slotIndex = i;
return false; // already seen
@@ -203,13 +203,13 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int GetHashCode(TId key)
+ private static int GetHashCode(TKey key)
{
// Specialized hash for known types - JIT eliminates branches
- if (IsInt32) return Unsafe.As(ref key);
- 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);
+ if (IsInt32) return Unsafe.As(ref key);
+ 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();
}
@@ -218,16 +218,16 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
{
// Use remembered capacity if larger (from previous serialization)
var actualCapacity = Math.Max(capacity, _bucketsLength);
-
+
_buckets = ArrayPool.Shared.Rent(actualCapacity);
- _bucketsLength = actualCapacity;
- Array.Fill(_buckets, -1, 0, actualCapacity);
+ _bucketsLength = _buckets.Length;
+ Array.Fill(_buckets, -1, 0, _bucketsLength);
_entries = ArrayPool.Shared.Rent(actualCapacity);
- _entriesLength = actualCapacity;
+ _entriesLength = _entries.Length;
//Array.Clear(_entries, 0, _entriesLength);
- _keys = ArrayPool.Shared.Rent(actualCapacity);
+ _keys = ArrayPool.Shared.Rent(actualCapacity);
//Array.Clear(_keys, 0, actualCapacity);
_count = 0;
@@ -239,9 +239,10 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
// Rent new arrays
var newBuckets = ArrayPool.Shared.Rent(newCapacity);
- Array.Fill(newBuckets, -1, 0, newCapacity); // MUST fill with -1
+ var newBucketsLength = newBuckets.Length;
+ Array.Fill(newBuckets, -1, 0, newBucketsLength);
var newEntries = ArrayPool.Shared.Rent(newCapacity);
- var newKeys = ArrayPool.Shared.Rent(newCapacity);
+ var newKeys = ArrayPool.Shared.Rent(newCapacity);
// Copy entries (no clear needed)
Array.Copy(_entries!, newEntries, _count);
@@ -250,7 +251,7 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
// Rebuild bucket chains
for (var i = 0; i < _count; i++)
{
- var bucketIdx = (GetHashCode(newKeys[i]) & 0x7FFFFFFF) % newCapacity;
+ var bucketIdx = (GetHashCode(newKeys[i]) & 0x7FFFFFFF) % newBucketsLength;
newEntries[i].Next = newBuckets[bucketIdx];
newBuckets[bucketIdx] = i;
}
@@ -258,22 +259,22 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
// Return old arrays to pool
ArrayPool.Shared.Return(_buckets!);
ArrayPool.Shared.Return(_entries!);
- ArrayPool.Shared.Return(_keys!);
+ ArrayPool.Shared.Return(_keys!);
_buckets = newBuckets;
- _bucketsLength = newCapacity;
+ _bucketsLength = newBucketsLength;
_entries = newEntries;
- _entriesLength = newCapacity;
+ _entriesLength = newEntries.Length;
_keys = newKeys;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool HasKey(TId key)
+ public bool HasKey(TKey key)
{
// Small int fast path - bitmap check is cache-friendly (512 bytes)
if (_useSmallInt && IsInt32)
{
- var intKey = Unsafe.As(ref key);
+ var intKey = Unsafe.As(ref key);
if ((uint)intKey < SmallSize && _smallBitmap != null)
{
var wordIdx = intKey >> 6;
@@ -298,87 +299,28 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
///
/// Checks if key exists and returns existing value, or adds new key (deserialization).
- /// Returns true if first occurrence (key was added, out = null).
+ /// Returns true if first occurrence (key was added, out = default).
/// Returns false if already seen (out = existing value).
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool TryGetOrAddKey(TId key, out object? existing)
+ public bool TryGetOrAddKey(TKey key, out TValue? existing)
{
- // NOTE: SmallInt path DISABLED for deserializer.
- // The 512KB _smallValues sparse array causes cache misses.
- // Hash table has better cache locality.
-
- //// Small int fast path
- //if (_useSmallInt && IsInt32)
- //{
- // var intKey = Unsafe.As(ref key);
- // if ((uint)intKey < SmallSize)
- // {
- // return TryGetOrAddSmallInt(intKey, out existing);
- // }
- //}
-
- // Hash table path
if (!TryAddHash(key, out var slotIndex))
{
existing = _entries![slotIndex].Value;
return false;
}
- existing = null;
- return true;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryGetOrAddSmallInt(int key, out object? existing)
- {
- // Lazy init bitmap only - NOT _smallValues!
- // This method is for tracking (key seen?), not value storage.
- if (_smallBitmap == null)
- {
- _smallBitmap = ArrayPool.Shared.Rent(SmallBitmapSize);
- Array.Clear(_smallBitmap, 0, SmallBitmapSize);
- }
-
- var wordIdx = key >> 6;
- var bit = 1UL << (key & 63);
-
- ref var word = ref _smallBitmap[wordIdx];
- if ((word & bit) != 0)
- {
- // Bitmap says seen - return stored value if any
- existing = _smallValues?[key];
- return false; // Already seen
- }
-
- // First occurrence - mark as seen (don't touch _smallValues!)
- word |= bit;
- existing = null;
+ existing = default;
return true;
}
///
- /// Gets existing value or adds new value for key (deserialization with object storage).
+ /// Gets existing value or adds new value for key (deserialization with value storage).
/// Returns the existing value if key was seen before, or stores and returns newValue.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public object TryGetOrAddValue(TId key, object newValue)
+ public TValue TryGetOrAddValue(TKey key, TValue newValue)
{
- // NOTE: SmallInt path DISABLED for deserializer value storage.
- // The 512KB _smallValues sparse array causes cache misses.
- // Hash table has better cache locality (only stores actual entries).
- // Bitmap is only useful for tracking (serializer) - not for value storage.
-
- //// Small int fast path
- //if (_useSmallInt && IsInt32)
- //{
- // var intKey = Unsafe.As(ref key);
- // if ((uint)intKey < SmallSize)
- // {
- // return TryGetOrAddSmallIntValue(intKey, newValue);
- // }
- //}
-
- // Hash table path
if (!TryAddHash(key, out var slotIndex))
{
var existing = _entries![slotIndex].Value;
@@ -388,71 +330,16 @@ public sealed class IdentityMap : IIdentityMap where TId : notnull
return newValue;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private object TryGetOrAddSmallIntValue(int key, object newValue)
- {
- // Lazy init bitmap
- if (_smallBitmap == null)
- {
- _smallBitmap = ArrayPool.Shared.Rent(SmallBitmapSize);
- Array.Clear(_smallBitmap, 0, SmallBitmapSize);
- }
-
- // Lazy init values array (only when needed for deserialization)
- if (_smallValues == null)
- {
- _smallValues = ArrayPool