diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Broadcaster/PeerInfo.cs b/src/Nethermind/Nethermind.AccountAbstraction/Broadcaster/PeerInfo.cs index e0b5a3970692..10d667788fff 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Broadcaster/PeerInfo.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Broadcaster/PeerInfo.cs @@ -12,7 +12,7 @@ public class PeerInfo : IUserOperationPoolPeer { private IUserOperationPoolPeer Peer { get; } - private ClockKeyCache NotifiedUserOperations { get; } = new(MemoryAllowance.MemPoolSize, "notifiedUserOperations"); + private ClockKeyCache NotifiedUserOperations { get; } = new(MemoryAllowance.MemPoolSize); public PeerInfo(IUserOperationPoolPeer peer) { diff --git a/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.cs index e7bda5dde5d7..8c6b57208389 100644 --- a/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.cs @@ -18,7 +18,7 @@ public class ClockCacheTests { private static Cache Create() { - return new Cache(Capacity, "test")!; + return new Cache(Capacity)!; } private const int Capacity = 32; @@ -183,7 +183,7 @@ public void Beyond_capacity_lru_check() [Test] public void Beyond_capacity_lru_parallel() { - Cache cache = new(Capacity, "test"); + Cache cache = new(Capacity); Parallel.For(0, Environment.ProcessorCount * 8, (iter) => { for (int ii = 0; ii < Capacity; ii++) @@ -275,7 +275,7 @@ public void Delete_keeps_internal_structure() int itemsToKeep = 10; int iterations = 40; - ClockCache cache = new(maxCapacity, "test"); + ClockCache cache = new(maxCapacity); for (int i = 0; i < iterations; i++) { diff --git a/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheNonConcurrentTests.cs b/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheNonConcurrentTests.cs index 86d5fa13910d..93a71f27b1f8 100644 --- a/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheNonConcurrentTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheNonConcurrentTests.cs @@ -31,7 +31,7 @@ public void Setup() [Test] public void At_capacity() { - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); for (int i = 0; i < Capacity; i++) { cache.Set(_addresses[i]).Should().BeTrue(); @@ -43,7 +43,7 @@ public void At_capacity() [Test] public void Can_reset() { - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); cache.Set(_addresses[0]).Should().BeTrue(); cache.Set(_addresses[0]).Should().BeFalse(); cache.Get(_addresses[0]).Should().BeTrue(); @@ -52,14 +52,14 @@ public void Can_reset() [Test] public void Can_ask_before_first_set() { - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); cache.Get(_addresses[0]).Should().BeFalse(); } [Test] public void Can_clear() { - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); cache.Set(_addresses[0]).Should().BeTrue(); cache.Clear(); cache.Get(_addresses[0]).Should().BeFalse(); @@ -70,7 +70,7 @@ public void Can_clear() [Test] public void Beyond_capacity() { - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); for (int i = 0; i < Capacity * 2; i++) { cache.Set(_addresses[i]); @@ -90,7 +90,7 @@ public void Beyond_capacity() [Test] public void Beyond_capacity_lru() { - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); for (int i = 0; i < Capacity * 2; i++) { for (int ii = 0; ii < Capacity / 2; ii++) @@ -105,7 +105,7 @@ public void Beyond_capacity_lru() public void Beyond_capacity_lru_check() { Random random = new(); - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); for (var iter = 0; iter < Capacity; iter++) { for (int ii = 0; ii < Capacity; ii++) @@ -172,7 +172,7 @@ public void Beyond_capacity_lru_check() [Test] public void Can_delete() { - ClockKeyCacheNonConcurrent cache = new(Capacity, "test"); + ClockKeyCacheNonConcurrent cache = new(Capacity); cache.Set(_addresses[0]).Should().BeTrue(); cache.Delete(_addresses[0]); cache.Get(_addresses[0]).Should().BeFalse(); diff --git a/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheTests.cs index 73f66ee3a80c..4c970f9afbc4 100644 --- a/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Caching/ClockKeyCacheTests.cs @@ -32,7 +32,7 @@ public void Setup() [Test] public void At_capacity() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); for (int i = 0; i < Capacity; i++) { cache.Set(_addresses[i]); @@ -44,7 +44,7 @@ public void At_capacity() [Test] public void Can_reset() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); cache.Set(_addresses[0]).Should().BeTrue(); cache.Set(_addresses[0]).Should().BeFalse(); cache.Get(_addresses[0]).Should().BeTrue(); @@ -53,14 +53,14 @@ public void Can_reset() [Test] public void Can_ask_before_first_set() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); cache.Get(_addresses[0]).Should().BeFalse(); } [Test] public void Can_clear() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); cache.Set(_addresses[0]).Should().BeTrue(); cache.Clear(); cache.Get(_addresses[0]).Should().BeFalse(); @@ -71,7 +71,7 @@ public void Can_clear() [Test] public void Beyond_capacity() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); for (int i = 0; i < Capacity * 2; i++) { cache.Set(_addresses[i]); @@ -91,7 +91,7 @@ public void Beyond_capacity() [Test] public void Beyond_capacity_lru() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); for (int i = 0; i < Capacity * 2; i++) { for (int ii = 0; ii < Capacity / 2; ii++) @@ -106,7 +106,7 @@ public void Beyond_capacity_lru() public void Beyond_capacity_lru_check() { Random random = new(); - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); for (var iter = 0; iter < Capacity; iter++) { for (int ii = 0; ii < Capacity; ii++) @@ -154,7 +154,7 @@ public void Beyond_capacity_lru_check() [Test] public void Beyond_capacity_lru_parallel() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); Parallel.For(0, Environment.ProcessorCount * 8, (s) => { for (int ii = 0; ii < Capacity; ii++) @@ -189,7 +189,7 @@ public void Beyond_capacity_lru_parallel() [Test] public void Can_delete() { - ClockKeyCache cache = new(Capacity, "test"); + ClockKeyCache cache = new(Capacity); cache.Set(_addresses[0]); cache.Delete(_addresses[0]); cache.Get(_addresses[0]).Should().BeFalse(); diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index 427a3539a1cf..3cdab2947f84 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -255,6 +255,10 @@ public readonly struct AddressAsKey(Address key) : IEquatable public bool Equals(AddressAsKey other) => _key == other._key; public override int GetHashCode() => _key?.GetHashCode() ?? 0; + public override string ToString() + { + return _key?.ToString() ?? ""; + } } public ref struct AddressStructRef diff --git a/src/Nethermind/Nethermind.Core/Caching/ClockCache.cs b/src/Nethermind/Nethermind.Core/Caching/ClockCache.cs index 9d8dda522264..57e0e146eee0 100644 --- a/src/Nethermind/Nethermind.Core/Caching/ClockCache.cs +++ b/src/Nethermind/Nethermind.Core/Caching/ClockCache.cs @@ -5,244 +5,152 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; using Nethermind.Core.Threading; -namespace Nethermind.Core.Caching +namespace Nethermind.Core.Caching; + +public sealed class ClockCache(int maxCapacity) : ClockCacheBase(maxCapacity) + where TKey : struct, IEquatable { - public sealed class ClockCache - where TKey : struct, IEquatable + private readonly ConcurrentDictionary _cacheMap = new ConcurrentDictionary(); + private readonly McsLock _lock = new(); + + public TValue Get(TKey key) { - private const int BitShiftPerInt64 = 6; - private readonly int _maxCapacity; - private readonly ConcurrentDictionary _cacheMap; - private readonly McsLock _lock = new(); - private readonly string _name; - private readonly TKey[] _keys; - private readonly long[] _accessedBitmap; - private readonly Queue _freeOffsets = new(); - - private int _clock = 0; - - public ClockCache(int maxCapacity, string name) + if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) { - ArgumentOutOfRangeException.ThrowIfLessThan(maxCapacity, 1); - - _name = name; - _maxCapacity = maxCapacity; - _cacheMap = new ConcurrentDictionary(); // do not initialize it at the full capacity - _keys = new TKey[maxCapacity]; - _accessedBitmap = new long[GetInt64ArrayLengthFromBitLength(maxCapacity)]; + MarkAccessed(ov.Offset); + return ov.Value; } + return default!; + } - public TValue Get(TKey key) + public bool TryGet(TKey key, out TValue value) + { + if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) { - if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) - { - MarkAccessed(ov.Offset); - return ov.Value; - } - return default!; + MarkAccessed(ov.Offset); + value = ov.Value; + return true; } - public bool TryGet(TKey key, out TValue value) + value = default!; + return false; + } + + public bool Set(TKey key, TValue val) + { + if (val is null) { - if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) - { - MarkAccessed(ov.Offset); - value = ov.Value; - return true; - } + return Delete(key); + } - value = default!; + if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) + { + ov.Value = val; + MarkAccessed(ov.Offset); return false; } - public bool Set(TKey key, TValue val) - { - if (val is null) - { - return Delete(key); - } + return SetSlow(key, val); + } - if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) - { - ov.Value = val; - MarkAccessed(ov.Offset); - return false; - } + private bool SetSlow(TKey key, TValue val) + { + using var lockRelease = _lock.Acquire(); - return SetSlow(key, val); + // Recheck under lock + if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) + { + ov.Value = val; + MarkAccessed(ov.Offset); + return false; } - private bool SetSlow(TKey key, TValue val) + int offset = _cacheMap.Count; + if (FreeOffsets.Count > 0) { - using var lockRelease = _lock.Acquire(); - - // Recheck under lock - if (_cacheMap.TryGetValue(key, out LruCacheItem? ov)) - { - ov.Value = val; - MarkAccessed(ov.Offset); - return false; - } - - int offset = _cacheMap.Count; - if (_freeOffsets.Count > 0) - { - offset = _freeOffsets.Dequeue(); - } - else if (offset >= _maxCapacity) - { - offset = Replace(key); - } - - _cacheMap[key] = new LruCacheItem(offset, val); - _keys[offset] = key; - - return true; + offset = FreeOffsets.Dequeue(); } - - private int Replace(TKey key) + else if (offset >= MaxCapacity) { - int position = _clock; - while (true) - { - if (position >= _maxCapacity) - { - position = 0; - } - - bool accessed = ClearAccessed(position); - if (!accessed) - { - if (!_cacheMap.TryRemove(_keys[position], out _)) - { - ThrowInvalidOperationException(); - } - break; - } + offset = Replace(key); + } - position++; - } + _cacheMap[key] = new LruCacheItem(offset, val); + KeyToOffset[offset] = key; - _clock = position + 1; - return position; + return true; + } - [DoesNotReturn] - static void ThrowInvalidOperationException() + private int Replace(TKey key) + { + int position = Clock; + int max = _cacheMap.Count; + while (true) + { + if (position >= max) { - throw new InvalidOperationException($"{nameof(ClockKeyCache)} removing item that doesn't exist"); + position = 0; } - } - - public bool Delete(TKey key) - { - using var lockRelease = _lock.Acquire(); - if (_cacheMap.Remove(key, out LruCacheItem? ov)) + bool accessed = ClearAccessed(position); + if (!accessed) { - _keys[ov.Offset] = default; - ClearAccessed(ov.Offset); - _freeOffsets.Enqueue(ov.Offset); - return true; + if (!_cacheMap.TryRemove(KeyToOffset[position], out _)) + { + ThrowInvalidOperationException(); + } + break; } - return false; + position++; } - public void Clear() - { - using var lockRelease = _lock.Acquire(); - - _clock = 0; - _cacheMap.Clear(); - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - _keys.AsSpan().Clear(); - } - _accessedBitmap.AsSpan().Clear(); - } + Clock = position + 1; + return position; - public bool Contains(TKey key) + [DoesNotReturn] + void ThrowInvalidOperationException() { - return _cacheMap.ContainsKey(key); + throw new InvalidOperationException($"{nameof(ClockKeyCache)} removing item {KeyToOffset[position]} at position {position} that doesn't exist"); } + } - public int Count => _cacheMap.Count; + public bool Delete(TKey key) + { + using var lockRelease = _lock.Acquire(); - private bool ClearAccessed(int position) + if (_cacheMap.Remove(key, out LruCacheItem? ov)) { - uint offset = (uint)position >> BitShiftPerInt64; - long flags = 1L << position; - - ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_accessedBitmap), offset); - bool accessed = (accessedBitmapWord & flags) != 0; + KeyToOffset[ov.Offset] = default; + ClearAccessed(ov.Offset); + FreeOffsets.Enqueue(ov.Offset); + return true; + } - if (accessed) - { - // Clear the accessed bit - flags = ~flags; - long current = Volatile.Read(ref accessedBitmapWord); - while (true) - { - long previous = Interlocked.CompareExchange(ref accessedBitmapWord, current & flags, current); - if (previous == current) - { - break; - } - current = previous; - } - } + return false; + } - return accessed; - } + public new void Clear() + { + using var lockRelease = _lock.Acquire(); - private void MarkAccessed(int position) - { - uint offset = (uint)position >> BitShiftPerInt64; - long flags = 1L << position; + base.Clear(); + _cacheMap.Clear(); + } - ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_accessedBitmap), offset); + public bool Contains(TKey key) + { + return _cacheMap.ContainsKey(key); + } - long current = Volatile.Read(ref accessedBitmapWord); - while (true) - { - long previous = Interlocked.CompareExchange(ref accessedBitmapWord, current | flags, current); - if (previous == current) - { - break; - } - current = previous; - } - } + public int Count => _cacheMap.Count; - /// - /// Used for conversion between different representations of bit array. - /// Returns (n + (64 - 1)) / 64, rearranged to avoid arithmetic overflow. - /// For example, in the bit to int case, the straightforward calc would - /// be (n + 63) / 64, but that would cause overflow. So instead it's - /// rearranged to ((n - 1) / 64) + 1. - /// Due to sign extension, we don't need to special case for n == 0, if we use - /// bitwise operations (since ((n - 1) >> 6) + 1 = 0). - /// This doesn't hold true for ((n - 1) / 64) + 1, which equals 1. - /// - /// Usage: - /// GetInt32ArrayLengthFromBitLength(77): returns how many ints must be - /// allocated to store 77 bits. - /// - /// - /// how many ints are required to store n bytes - private static int GetInt64ArrayLengthFromBitLength(int n) => - (n - 1 + (1 << BitShiftPerInt64)) >>> BitShiftPerInt64; - - private class LruCacheItem(int offset, TValue v) - { - public readonly int Offset = offset; - public TValue Value = v; - } + private class LruCacheItem(int offset, TValue v) + { + public readonly int Offset = offset; + public TValue Value = v; } } diff --git a/src/Nethermind/Nethermind.Core/Caching/ClockCacheBase.cs b/src/Nethermind/Nethermind.Core/Caching/ClockCacheBase.cs new file mode 100644 index 000000000000..e494b616bbbd --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Caching/ClockCacheBase.cs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Nethermind.Core.Caching; + +public abstract class ClockCacheBase + where TKey : struct, IEquatable +{ + protected const int BitShiftPerInt64 = 6; + + protected int MaxCapacity { get; } + + protected TKey[] KeyToOffset { get; } + protected long[] HasBeenAccessedBitmap { get; } + protected Queue FreeOffsets { get; } = new(); + + protected int Clock { get; set; } = 0; + + protected ClockCacheBase(int maxCapacity) + { + ArgumentOutOfRangeException.ThrowIfLessThan(maxCapacity, 1); + + MaxCapacity = maxCapacity; + KeyToOffset = new TKey[maxCapacity]; + HasBeenAccessedBitmap = new long[GetInt64ArrayLengthFromBitLength(maxCapacity)]; + } + + protected void Clear() + { + Clock = 0; + FreeOffsets.Clear(); + KeyToOffset.AsSpan().Clear(); + HasBeenAccessedBitmap.AsSpan().Clear(); + } + + protected bool ClearAccessed(int position) + { + uint offset = (uint)position >> BitShiftPerInt64; + long flags = 1L << position; + + ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(HasBeenAccessedBitmap), offset); + bool accessed = (accessedBitmapWord & flags) != 0; + + if (accessed) + { + // Clear the accessed bit + flags = ~flags; + long current = Volatile.Read(ref accessedBitmapWord); + while (true) + { + long previous = Interlocked.CompareExchange(ref accessedBitmapWord, current & flags, current); + if (previous == current) + { + break; + } + current = previous; + } + } + + return accessed; + } + + protected void MarkAccessed(int position) + { + uint offset = (uint)position >> BitShiftPerInt64; + long flags = 1L << position; + + ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(HasBeenAccessedBitmap), offset); + + long current = Volatile.Read(ref accessedBitmapWord); + while (true) + { + long previous = Interlocked.CompareExchange(ref accessedBitmapWord, current | flags, current); + if (previous == current) + { + break; + } + current = previous; + } + } + + /// + /// Used for conversion between different representations of bit array. + /// Returns (n + (64 - 1)) / 64, rearranged to avoid arithmetic overflow. + /// For example, in the bit to int case, the straightforward calc would + /// be (n + 63) / 64, but that would cause overflow. So instead it's + /// rearranged to ((n - 1) / 64) + 1. + /// Due to sign extension, we don't need to special case for n == 0, if we use + /// bitwise operations (since ((n - 1) >> 6) + 1 = 0). + /// This doesn't hold true for ((n - 1) / 64) + 1, which equals 1. + /// + /// Usage: + /// GetInt32ArrayLengthFromBitLength(77): returns how many ints must be + /// allocated to store 77 bits. + /// + /// + /// how many ints are required to store n bytes + private static int GetInt64ArrayLengthFromBitLength(int n) => + (n - 1 + (1 << BitShiftPerInt64)) >>> BitShiftPerInt64; +} diff --git a/src/Nethermind/Nethermind.Core/Caching/ClockKeyCache.cs b/src/Nethermind/Nethermind.Core/Caching/ClockKeyCache.cs index ec071b96cf86..d8b1eaf64696 100644 --- a/src/Nethermind/Nethermind.Core/Caching/ClockKeyCache.cs +++ b/src/Nethermind/Nethermind.Core/Caching/ClockKeyCache.cs @@ -5,217 +5,125 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; using Nethermind.Core.Threading; -namespace Nethermind.Core.Caching +namespace Nethermind.Core.Caching; + +public sealed class ClockKeyCache(int maxCapacity) : ClockCacheBase(maxCapacity) + where TKey : struct, IEquatable { - public sealed class ClockKeyCache - where TKey : struct, IEquatable + private readonly ConcurrentDictionary _cacheMap = new ConcurrentDictionary(); + private readonly McsLock _lock = new(); + + public bool Get(TKey key) { - private const int BitShiftPerInt64 = 6; - private readonly int _maxCapacity; - private readonly ConcurrentDictionary _cacheMap; - private readonly McsLock _lock = new(); - private readonly string _name; - private readonly TKey[] _keys; - private readonly long[] _accessedBitmap; - private readonly Queue _freeOffsets = new(); - - private int _clock = 0; - - public ClockKeyCache(int maxCapacity, string name) + if (_cacheMap.TryGetValue(key, out int offset)) { - ArgumentOutOfRangeException.ThrowIfLessThan(maxCapacity, 1); - - _name = name; - _maxCapacity = maxCapacity; - _cacheMap = new ConcurrentDictionary(); // do not initialize it at the full capacity - _keys = new TKey[maxCapacity]; - _accessedBitmap = new long[GetInt64ArrayLengthFromBitLength(maxCapacity)]; + MarkAccessed(offset); + return true; } + return false; + } - public bool Get(TKey key) + public bool Set(TKey key) + { + if (_cacheMap.TryGetValue(key, out int offset)) { - if (_cacheMap.TryGetValue(key, out int offset)) - { - MarkAccessed(offset); - return true; - } + MarkAccessed(offset); return false; } - public bool Set(TKey key) - { - if (_cacheMap.TryGetValue(key, out int offset)) - { - MarkAccessed(offset); - return false; - } + return SetSlow(key); + } - return SetSlow(key); - } + private bool SetSlow(TKey key) + { + using var lockRelease = _lock.Acquire(); - private bool SetSlow(TKey key) + // Recheck under lock + if (_cacheMap.TryGetValue(key, out int offset)) { - using var lockRelease = _lock.Acquire(); - - // Recheck under lock - if (_cacheMap.TryGetValue(key, out int offset)) - { - MarkAccessed(offset); - return false; - } - - offset = _cacheMap.Count; - if (_freeOffsets.Count > 0) - { - offset = _freeOffsets.Dequeue(); - } - else if (offset >= _maxCapacity) - { - offset = Replace(key); - } - - _cacheMap[key] = offset; - _keys[offset] = key; - - return true; + MarkAccessed(offset); + return false; } - private int Replace(TKey key) + offset = _cacheMap.Count; + if (FreeOffsets.Count > 0) { - int position = _clock; - while (true) - { - if (position >= _maxCapacity) - { - position = 0; - } - - bool accessed = ClearAccessed(position); - if (!accessed) - { - if (!_cacheMap.TryRemove(_keys[position], out _)) - { - ThrowInvalidOperationException(); - } - break; - } + offset = FreeOffsets.Dequeue(); + } + else if (offset >= MaxCapacity) + { + offset = Replace(key); + } - position++; - } + _cacheMap[key] = offset; + KeyToOffset[offset] = key; - _clock = position + 1; - return position; + return true; + } - [DoesNotReturn] - static void ThrowInvalidOperationException() + private int Replace(TKey key) + { + int position = Clock; + int max = _cacheMap.Count; + while (true) + { + if (position >= max) { - throw new InvalidOperationException($"{nameof(ClockKeyCache)} removing item that doesn't exist"); + position = 0; } - } - public bool Delete(TKey key) - { - using var lockRelease = _lock.Acquire(); - - if (_cacheMap.Remove(key, out int offset)) + bool accessed = ClearAccessed(position); + if (!accessed) { - ClearAccessed(offset); - _freeOffsets.Enqueue(offset); - return true; + if (!_cacheMap.TryRemove(KeyToOffset[position], out _)) + { + ThrowInvalidOperationException(); + } + break; } - return false; + position++; } - public void Clear() - { - using var lockRelease = _lock.Acquire(); - - _clock = 0; - _cacheMap.Clear(); - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - _keys.AsSpan().Clear(); - } - _accessedBitmap.AsSpan().Clear(); - } + Clock = position + 1; + return position; - public bool Contains(TKey key) + [DoesNotReturn] + static void ThrowInvalidOperationException() { - return _cacheMap.ContainsKey(key); + throw new InvalidOperationException($"{nameof(ClockKeyCache)} removing item that doesn't exist"); } + } - public int Count => _cacheMap.Count; + public bool Delete(TKey key) + { + using var lockRelease = _lock.Acquire(); - private bool ClearAccessed(int position) + if (_cacheMap.Remove(key, out int offset)) { - uint offset = (uint)position >> BitShiftPerInt64; - long flags = 1L << position; - - ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_accessedBitmap), offset); - bool accessed = (accessedBitmapWord & flags) != 0; - - if (accessed) - { - // Clear the accessed bit - flags = ~flags; - long current = Volatile.Read(ref accessedBitmapWord); - while (true) - { - long previous = Interlocked.CompareExchange(ref accessedBitmapWord, current & flags, current); - if (previous == current) - { - break; - } - current = previous; - } - } - - return accessed; + ClearAccessed(offset); + FreeOffsets.Enqueue(offset); + return true; } - private void MarkAccessed(int position) - { - uint offset = (uint)position >> BitShiftPerInt64; - long flags = 1L << position; + return false; + } - ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_accessedBitmap), offset); + public new void Clear() + { + using var lockRelease = _lock.Acquire(); - long current = Volatile.Read(ref accessedBitmapWord); - while (true) - { - long previous = Interlocked.CompareExchange(ref accessedBitmapWord, current | flags, current); - if (previous == current) - { - break; - } - current = previous; - } - } + base.Clear(); + _cacheMap.Clear(); + } - /// - /// Used for conversion between different representations of bit array. - /// Returns (n + (64 - 1)) / 64, rearranged to avoid arithmetic overflow. - /// For example, in the bit to int case, the straightforward calc would - /// be (n + 63) / 64, but that would cause overflow. So instead it's - /// rearranged to ((n - 1) / 64) + 1. - /// Due to sign extension, we don't need to special case for n == 0, if we use - /// bitwise operations (since ((n - 1) >> 6) + 1 = 0). - /// This doesn't hold true for ((n - 1) / 64) + 1, which equals 1. - /// - /// Usage: - /// GetInt32ArrayLengthFromBitLength(77): returns how many ints must be - /// allocated to store 77 bits. - /// - /// - /// how many ints are required to store n bytes - private static int GetInt64ArrayLengthFromBitLength(int n) => - (n - 1 + (1 << BitShiftPerInt64)) >>> BitShiftPerInt64; + public bool Contains(TKey key) + { + return _cacheMap.ContainsKey(key); } + + public int Count => _cacheMap.Count; } diff --git a/src/Nethermind/Nethermind.Core/Caching/ClockKeyCacheNonConcurrent.cs b/src/Nethermind/Nethermind.Core/Caching/ClockKeyCacheNonConcurrent.cs index 27946abe416a..afe86cc3981c 100644 --- a/src/Nethermind/Nethermind.Core/Caching/ClockKeyCacheNonConcurrent.cs +++ b/src/Nethermind/Nethermind.Core/Caching/ClockKeyCacheNonConcurrent.cs @@ -6,175 +6,131 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -namespace Nethermind.Core.Caching -{ - public sealed class ClockKeyCacheNonConcurrent - where TKey : struct, IEquatable - { - private const int BitShiftPerInt64 = 6; - private readonly int _maxCapacity; - private readonly Dictionary _cacheMap; - private readonly string _name; - private readonly TKey[] _items; - private readonly long[] _accessedBitmap; - private readonly Queue _freeOffsets = new(); +namespace Nethermind.Core.Caching; - private int _clock = 0; +public sealed class ClockKeyCacheNonConcurrent(int maxCapacity) : ClockCacheBase(maxCapacity) + where TKey : struct, IEquatable +{ + private readonly Dictionary _cacheMap = new(); - public ClockKeyCacheNonConcurrent(int maxCapacity, string name) + public bool Get(TKey key) + { + if (_cacheMap.TryGetValue(key, out int offset)) { - ArgumentOutOfRangeException.ThrowIfLessThan(maxCapacity, 1); - - _name = name; - _maxCapacity = maxCapacity; - _cacheMap = new Dictionary(maxCapacity / 2); // do not initialize it at the full capacity - _items = new TKey[maxCapacity]; - _accessedBitmap = new long[GetInt64ArrayLengthFromBitLength(maxCapacity)]; + MarkAccessedNonConcurrent(offset); + return true; } + return false; + } - public bool Get(TKey key) + public bool Set(TKey key) + { + if (_cacheMap.TryGetValue(key, out int offset)) { - if (_cacheMap.TryGetValue(key, out int offset)) - { - MarkAccessed(offset); - return true; - } + MarkAccessedNonConcurrent(offset); return false; } - public bool Set(TKey key) - { - if (_cacheMap.TryGetValue(key, out int offset)) - { - MarkAccessed(offset); - return false; - } + return SetSlow(key); + } - return SetSlow(key); + private bool SetSlow(TKey key) + { + int offset = _cacheMap.Count; + if (FreeOffsets.Count > 0) + { + offset = FreeOffsets.Dequeue(); } - - private bool SetSlow(TKey key) + else if (offset >= MaxCapacity) { - int offset = _cacheMap.Count; - if (_freeOffsets.Count > 0) - { - offset = _freeOffsets.Dequeue(); - } - else if (offset >= _maxCapacity) - { - offset = Replace(key); - } + offset = Replace(key); + } - _cacheMap[key] = offset; - _items[offset] = key; + _cacheMap[key] = offset; + KeyToOffset[offset] = key; - return true; - } + return true; + } - private int Replace(TKey key) + private int Replace(TKey key) + { + int position = Clock; + int max = _cacheMap.Count; + while (true) { - int position = _clock; - while (true) + if (position >= max) { - if (position >= _maxCapacity) - { - position = 0; - } + position = 0; + } - bool accessed = ClearAccessed(position); - if (!accessed) + bool accessed = ClearAccessedNonConcurrent(position); + if (!accessed) + { + if (!_cacheMap.Remove(KeyToOffset[position])) { - if (!_cacheMap.Remove(_items[position])) - { - ThrowInvalidOperationException(); - } - break; + ThrowInvalidOperationException(); } - - position++; + break; } - _clock = position + 1; - return position; - - [DoesNotReturn] - static void ThrowInvalidOperationException() - { - throw new InvalidOperationException($"{nameof(ClockKeyCache)} removing item that doesn't exist"); - } + position++; } - public bool Delete(TKey key) - { - if (_cacheMap.Remove(key, out int offset)) - { - ref var node = ref _items[offset]; - ClearAccessed(offset); - _freeOffsets.Enqueue(offset); - return true; - } + Clock = position + 1; + return position; - return false; - } - - public void Clear() + [DoesNotReturn] + static void ThrowInvalidOperationException() { - _clock = 0; - _cacheMap.Clear(); - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - _items.AsSpan().Clear(); - } - _accessedBitmap.AsSpan().Clear(); + throw new InvalidOperationException($"{nameof(ClockKeyCache)} removing item that doesn't exist"); } + } - public bool Contains(TKey key) + public bool Delete(TKey key) + { + if (_cacheMap.Remove(key, out int offset)) { - return _cacheMap.ContainsKey(key); + ref var node = ref KeyToOffset[offset]; + ClearAccessedNonConcurrent(offset); + FreeOffsets.Enqueue(offset); + return true; } - public int Count => _cacheMap.Count; + return false; + } - private bool ClearAccessed(int position) - { - uint offset = (uint)position >> BitShiftPerInt64; - long flags = 1L << position; + public new void Clear() + { + base.Clear(); + _cacheMap.Clear(); + } - ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_accessedBitmap), offset); - bool accessed = (accessedBitmapWord & flags) != 0; - accessedBitmapWord &= ~flags; + public bool Contains(TKey key) + { + return _cacheMap.ContainsKey(key); + } - return accessed; - } + public int Count => _cacheMap.Count; - private void MarkAccessed(int position) - { - uint offset = (uint)position >> BitShiftPerInt64; - long flags = 1L << position; + private bool ClearAccessedNonConcurrent(int position) + { + uint offset = (uint)position >> BitShiftPerInt64; + long flags = 1L << position; - ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_accessedBitmap), offset); - accessedBitmapWord |= flags; - } + ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(HasBeenAccessedBitmap), offset); + bool accessed = (accessedBitmapWord & flags) != 0; + accessedBitmapWord &= ~flags; + + return accessed; + } + + private void MarkAccessedNonConcurrent(int position) + { + uint offset = (uint)position >> BitShiftPerInt64; + long flags = 1L << position; - /// - /// Used for conversion between different representations of bit array. - /// Returns (n + (64 - 1)) / 64, rearranged to avoid arithmetic overflow. - /// For example, in the bit to int case, the straightforward calc would - /// be (n + 63) / 64, but that would cause overflow. So instead it's - /// rearranged to ((n - 1) / 64) + 1. - /// Due to sign extension, we don't need to special case for n == 0, if we use - /// bitwise operations (since ((n - 1) >> 6) + 1 = 0). - /// This doesn't hold true for ((n - 1) / 64) + 1, which equals 1. - /// - /// Usage: - /// GetInt32ArrayLengthFromBitLength(77): returns how many ints must be - /// allocated to store 77 bits. - /// - /// - /// how many ints are required to store n bytes - private static int GetInt64ArrayLengthFromBitLength(int n) => - (n - 1 + (1 << BitShiftPerInt64)) >>> BitShiftPerInt64; + ref long accessedBitmapWord = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(HasBeenAccessedBitmap), offset); + accessedBitmapWord |= flags; } } diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 20b389b9642e..3d9c4e04af15 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -33,7 +33,7 @@ public CodeLruCache() for (int i = 0; i < _caches.Length; i++) { // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount, $"VM bytecodes {i}"); + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); } } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs index 2ab2a7fbc354..a0ec107e7bcd 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs @@ -75,7 +75,7 @@ public abstract class SyncPeerProtocolHandlerBase : ZeroProtocolHandlerBase, ISy ); protected ClockKeyCache? _notifiedTransactions; - protected ClockKeyCache NotifiedTransactions => _notifiedTransactions ??= new(2 * MemoryAllowance.MemPoolSize, "notifiedTransactions"); + protected ClockKeyCache NotifiedTransactions => _notifiedTransactions ??= new(2 * MemoryAllowance.MemPoolSize); protected SyncPeerProtocolHandlerBase(ISession session, IMessageSerializationService serializer, diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/PooledTxsRequestor.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/PooledTxsRequestor.cs index f0a9d8f8e03b..4689760db460 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/PooledTxsRequestor.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/PooledTxsRequestor.cs @@ -19,7 +19,7 @@ public class PooledTxsRequestor : IPooledTxsRequestor private readonly ITxPool _txPool; private readonly ITxPoolConfig _txPoolConfig; - private readonly ClockKeyCache _pendingHashes = new(MemoryAllowance.TxHashCacheSize, "pending tx hashes"); + private readonly ClockKeyCache _pendingHashes = new(MemoryAllowance.TxHashCacheSize); public PooledTxsRequestor(ITxPool txPool, ITxPoolConfig txPoolConfig) { diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs index 4601c87f04e0..81cd907963b0 100644 --- a/src/Nethermind/Nethermind.State/StateProvider.cs +++ b/src/Nethermind/Nethermind.State/StateProvider.cs @@ -30,7 +30,7 @@ internal class StateProvider // Note: // False negatives are fine as they will just result in a overwrite set // False positives would be problematic as the code _must_ be persisted - private readonly ClockKeyCacheNonConcurrent _codeInsertFilter = new(1_024, "Code Insert Filter"); + private readonly ClockKeyCacheNonConcurrent _codeInsertFilter = new(1_024); private readonly Dictionary _blockCache = new(4_096); private readonly ConcurrentDictionary? _preBlockCache; diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs index 40f0335668cb..afb19297673d 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs @@ -31,7 +31,7 @@ public class SnapProvider : ISnapProvider private readonly ProgressTracker _progressTracker; // This is actually close to 97% effective. - private readonly ClockKeyCache _codeExistKeyCache = new(1024 * 16, ""); + private readonly ClockKeyCache _codeExistKeyCache = new(1024 * 16); public SnapProvider(ProgressTracker progressTracker, IDb codeDb, INodeStorage nodeStorage, ILogManager logManager) { diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 7541f2fef88f..9d7972ec11c4 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -324,7 +324,7 @@ public TrieStore( if (pruningStrategy.TrackedPastKeyCount > 0 && nodeStorage.RequirePath) { - _pastPathHash = new(pruningStrategy.TrackedPastKeyCount, ""); + _pastPathHash = new(pruningStrategy.TrackedPastKeyCount); } else { diff --git a/src/Nethermind/Nethermind.TxPool/HashCache.cs b/src/Nethermind/Nethermind.TxPool/HashCache.cs index a0217f091df2..b800a1591fe5 100644 --- a/src/Nethermind/Nethermind.TxPool/HashCache.cs +++ b/src/Nethermind/Nethermind.TxPool/HashCache.cs @@ -24,12 +24,10 @@ internal class HashCache private const int SafeCapacity = 1024 * 16; private readonly ClockKeyCache _longTermCache = new( - MemoryAllowance.TxHashCacheSize, - "long term hash cache"); + MemoryAllowance.TxHashCacheSize); private readonly ClockKeyCache _currentBlockCache = new( - SafeCapacity, - "current block hash cache"); + SafeCapacity); public bool Get(Hash256 hash) { diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 89cb5342f9e3..7b78f8d32af5 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -782,7 +782,7 @@ public AccountCache(IAccountStateProvider provider) for (int i = 0; i < _caches.Length; i++) { // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(1_024, ""); + _caches[i] = new ClockCache(1_024); } }