Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intra-block cache #7039

Merged
merged 8 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions src/Nethermind/Nethermind.Core/Caching/LruCacheNonConcurrent.cs
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 src/Nethermind/Nethermind.Core/Caching/LruKeyCacheNonConcurrent.cs
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);
}
}
}
18 changes: 16 additions & 2 deletions src/Nethermind/Nethermind.Db/Metrics.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using Nethermind.Core.Attributes;

Expand All @@ -14,10 +12,18 @@ public static class Metrics
[Description("Number of Code DB cache reads.")]
public static long CodeDbCache { get; set; }

[CounterMetric]
[Description("Number of State Trie cache hits.")]
public static long StateTreeCache { get; set; }

[CounterMetric]
[Description("Number of State Trie reads.")]
public static long StateTreeReads { get; set; }

[CounterMetric]
[Description("Number of State Reader reads.")]
public static long StateReaderReads { get; set; }

[CounterMetric]
[Description("Number of Blocks Trie writes.")]
public static long StateTreeWrites { get; set; }
Expand All @@ -26,10 +32,18 @@ public static class Metrics
[Description("Number of State DB duplicate writes during full pruning.")]
public static int StateDbInPruningWrites;

[CounterMetric]
[Description("Number of storage trie cache hits.")]
public static long StorageTreeCache { get; set; }

[CounterMetric]
[Description("Number of storage trie reads.")]
public static long StorageTreeReads { get; set; }

[CounterMetric]
[Description("Number of storage reader reads.")]
public static long StorageReaderReads { get; set; }

[CounterMetric]
[Description("Number of storage trie writes.")]
public static long StorageTreeWrites { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Evm/MemoryAllowance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace Nethermind.Evm
{
public static class MemoryAllowance
{
public static int CodeCacheSize { get; } = 16_384;
public static int CodeCacheSize { get; } = 4_096 + 1_024;
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.State/IWorldState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public interface IWorldState : IJournal<Snapshot>, IReadOnlyStateProvider

void Commit(IReleaseSpec releaseSpec, bool isGenesis = false);

void Commit(IReleaseSpec releaseSpec, IWorldStateTracer? traver, bool isGenesis = false);
void Commit(IReleaseSpec releaseSpec, IWorldStateTracer? tracer, bool isGenesis = false);

void CommitTree(long blockNumber);
}
Loading