-
Notifications
You must be signed in to change notification settings - Fork 464
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
347 additions
and
31 deletions.
There are no files selected for viewing
163 changes: 163 additions & 0 deletions
163
src/Nethermind/Nethermind.Core/Caching/LruCacheNonConcurrent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Nethermind.Core.Extensions; | ||
|
||
namespace Nethermind.Core.Caching | ||
{ | ||
public sealed class LruCacheNonConcurrent<TKey, TValue> where TKey : notnull | ||
{ | ||
private readonly int _maxCapacity; | ||
private readonly Dictionary<TKey, LinkedListNode<LruCacheItem>> _cacheMap; | ||
private readonly string _name; | ||
private LinkedListNode<LruCacheItem>? _leastRecentlyUsed; | ||
|
||
public LruCacheNonConcurrent(int maxCapacity, int startCapacity, string name) | ||
{ | ||
ArgumentOutOfRangeException.ThrowIfLessThan(maxCapacity, 1); | ||
|
||
_name = name; | ||
_maxCapacity = maxCapacity; | ||
_cacheMap = typeof(TKey) == typeof(byte[]) | ||
? new Dictionary<TKey, LinkedListNode<LruCacheItem>>((IEqualityComparer<TKey>)Bytes.EqualityComparer) | ||
: new Dictionary<TKey, LinkedListNode<LruCacheItem>>(startCapacity); // do not initialize it at the full capacity | ||
} | ||
|
||
public LruCacheNonConcurrent(int maxCapacity, string name) | ||
: this(maxCapacity, 0, name) | ||
{ | ||
} | ||
|
||
public void Clear() | ||
{ | ||
_leastRecentlyUsed = null; | ||
_cacheMap.Clear(); | ||
} | ||
|
||
public TValue Get(TKey key) | ||
{ | ||
if (_cacheMap.TryGetValue(key, out LinkedListNode<LruCacheItem>? node)) | ||
{ | ||
TValue value = node.Value.Value; | ||
LinkedListNode<LruCacheItem>.MoveToMostRecent(ref _leastRecentlyUsed, node); | ||
return value; | ||
} | ||
|
||
#pragma warning disable 8603 | ||
// fixed C# 9 | ||
return default; | ||
#pragma warning restore 8603 | ||
} | ||
|
||
public bool TryGet(TKey key, out TValue value) | ||
{ | ||
if (_cacheMap.TryGetValue(key, out LinkedListNode<LruCacheItem>? node)) | ||
{ | ||
value = node.Value.Value; | ||
LinkedListNode<LruCacheItem>.MoveToMostRecent(ref _leastRecentlyUsed, node); | ||
return true; | ||
} | ||
|
||
#pragma warning disable 8601 | ||
// fixed C# 9 | ||
value = default; | ||
#pragma warning restore 8601 | ||
return false; | ||
} | ||
|
||
public bool Set(TKey key, TValue val) | ||
{ | ||
if (val is null) | ||
{ | ||
return DeleteNoLock(key); | ||
} | ||
|
||
if (_cacheMap.TryGetValue(key, out LinkedListNode<LruCacheItem>? node)) | ||
{ | ||
node.Value.Value = val; | ||
LinkedListNode<LruCacheItem>.MoveToMostRecent(ref _leastRecentlyUsed, node); | ||
return false; | ||
} | ||
|
||
if (_cacheMap.Count >= _maxCapacity) | ||
{ | ||
Replace(key, val); | ||
} | ||
else | ||
{ | ||
LinkedListNode<LruCacheItem> newNode = new(new(key, val)); | ||
LinkedListNode<LruCacheItem>.AddMostRecent(ref _leastRecentlyUsed, newNode); | ||
_cacheMap.Add(key, newNode); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
public bool DeleteNoLock(TKey key) | ||
{ | ||
if (_cacheMap.TryGetValue(key, out LinkedListNode<LruCacheItem>? node)) | ||
{ | ||
LinkedListNode<LruCacheItem>.Remove(ref _leastRecentlyUsed, node); | ||
_cacheMap.Remove(key); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public bool Contains(TKey key) | ||
{ | ||
return _cacheMap.ContainsKey(key); | ||
} | ||
|
||
public int Size | ||
{ | ||
get | ||
{ | ||
return _cacheMap.Count; | ||
} | ||
} | ||
|
||
private void Replace(TKey key, TValue value) | ||
{ | ||
LinkedListNode<LruCacheItem>? node = _leastRecentlyUsed; | ||
if (node is null) | ||
{ | ||
ThrowInvalidOperationException(); | ||
} | ||
|
||
_cacheMap.Remove(node!.Value.Key); | ||
|
||
node.Value = new(key, value); | ||
LinkedListNode<LruCacheItem>.MoveToMostRecent(ref _leastRecentlyUsed, node); | ||
_cacheMap.Add(key, node); | ||
|
||
[DoesNotReturn] | ||
static void ThrowInvalidOperationException() | ||
{ | ||
throw new InvalidOperationException( | ||
$"{nameof(LruCache<TKey, TValue>)} called {nameof(Replace)} when empty."); | ||
} | ||
} | ||
|
||
private struct LruCacheItem(TKey k, TValue v) | ||
{ | ||
public readonly TKey Key = k; | ||
public TValue Value = v; | ||
} | ||
|
||
public long MemorySize => CalculateMemorySize(0, _cacheMap.Count); | ||
|
||
public static long CalculateMemorySize(int keyPlusValueSize, int currentItemsCount) | ||
{ | ||
// it may actually be different if the initial capacity not equal to max (depending on the dictionary growth path) | ||
|
||
const int preInit = 48 /* LinkedList */ + 80 /* Dictionary */ + 24; | ||
int postInit = 52 /* lazy init of two internal dictionary arrays + dictionary size times (entry size + int) */ + MemorySizes.FindNextPrime(currentItemsCount) * 28 + currentItemsCount * 80 /* LinkedListNode and CacheItem times items count */; | ||
return MemorySizes.Align(preInit + postInit + keyPlusValueSize * currentItemsCount); | ||
} | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
src/Nethermind/Nethermind.Core/Caching/LruKeyCacheNonConcurrent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Nethermind.Core.Extensions; | ||
|
||
namespace Nethermind.Core.Caching | ||
{ | ||
public sealed class LruKeyCacheNonConcurrent<TKey> where TKey : notnull | ||
{ | ||
private readonly int _maxCapacity; | ||
private readonly string _name; | ||
private readonly Dictionary<TKey, LinkedListNode<TKey>> _cacheMap; | ||
private LinkedListNode<TKey>? _leastRecentlyUsed; | ||
|
||
public LruKeyCacheNonConcurrent(int maxCapacity, int startCapacity, string name) | ||
{ | ||
_maxCapacity = maxCapacity; | ||
_name = name ?? throw new ArgumentNullException(nameof(name)); | ||
_cacheMap = typeof(TKey) == typeof(byte[]) | ||
? new Dictionary<TKey, LinkedListNode<TKey>>((IEqualityComparer<TKey>)Bytes.EqualityComparer) | ||
: new Dictionary<TKey, LinkedListNode<TKey>>(startCapacity); // do not initialize it at the full capacity | ||
} | ||
|
||
public LruKeyCacheNonConcurrent(int maxCapacity, string name) | ||
: this(maxCapacity, 0, name) | ||
{ | ||
} | ||
|
||
public void Clear() | ||
{ | ||
_leastRecentlyUsed = null; | ||
_cacheMap.Clear(); | ||
} | ||
|
||
public bool Get(TKey key) | ||
{ | ||
if (_cacheMap.TryGetValue(key, out LinkedListNode<TKey>? node)) | ||
{ | ||
LinkedListNode<TKey>.MoveToMostRecent(ref _leastRecentlyUsed, node); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public bool Set(TKey key) | ||
{ | ||
if (_cacheMap.TryGetValue(key, out LinkedListNode<TKey>? node)) | ||
{ | ||
LinkedListNode<TKey>.MoveToMostRecent(ref _leastRecentlyUsed, node); | ||
return false; | ||
} | ||
else | ||
{ | ||
if (_cacheMap.Count >= _maxCapacity) | ||
{ | ||
Replace(key); | ||
} | ||
else | ||
{ | ||
LinkedListNode<TKey> newNode = new(key); | ||
LinkedListNode<TKey>.AddMostRecent(ref _leastRecentlyUsed, newNode); | ||
_cacheMap.Add(key, newNode); | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
|
||
public void Delete(TKey key) | ||
{ | ||
if (_cacheMap.TryGetValue(key, out LinkedListNode<TKey>? node)) | ||
{ | ||
LinkedListNode<TKey>.Remove(ref _leastRecentlyUsed, node); | ||
_cacheMap.Remove(key); | ||
} | ||
} | ||
|
||
private void Replace(TKey key) | ||
{ | ||
LinkedListNode<TKey>? node = _leastRecentlyUsed; | ||
if (node is null) | ||
{ | ||
ThrowInvalidOperation(); | ||
} | ||
|
||
_cacheMap.Remove(node.Value); | ||
node.Value = key; | ||
LinkedListNode<TKey>.MoveToMostRecent(ref _leastRecentlyUsed, node); | ||
_cacheMap.Add(key, node); | ||
|
||
[DoesNotReturn] | ||
static void ThrowInvalidOperation() | ||
{ | ||
throw new InvalidOperationException( | ||
$"{nameof(LruKeyCache<TKey>)} called {nameof(Replace)} when empty."); | ||
} | ||
} | ||
|
||
public long MemorySize => CalculateMemorySize(0, _cacheMap.Count); | ||
|
||
// TODO: memory size on the KeyCache will be smaller because we do not create LruCacheItems | ||
public static long CalculateMemorySize(int keyPlusValueSize, int currentItemsCount) | ||
{ | ||
// it may actually be different if the initial capacity not equal to max (depending on the dictionary growth path) | ||
|
||
const int preInit = 48 /* LinkedList */ + 80 /* Dictionary */ + 24; | ||
int postInit = 52 /* lazy init of two internal dictionary arrays + dictionary size times (entry size + int) */ + MemorySizes.FindNextPrime(currentItemsCount) * 28 + currentItemsCount * 80 /* LinkedListNode and CacheItem times items count */; | ||
return MemorySizes.Align(preInit + postInit + keyPlusValueSize * currentItemsCount); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.