Skip to content

Commit

Permalink
Reduce collection resizing costs (#7360)
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams authored Aug 29, 2024
1 parent abde872 commit 29f2a20
Show file tree
Hide file tree
Showing 17 changed files with 281 additions and 260 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public Task PreWarmCaches(Block suggestedBlock, Hash256? parentStateRoot, Cancel

public void ClearCaches() => targetWorldState?.ClearCache();

public Task ClearCachesInBackground() => targetWorldState?.ClearCachesInBackground() ?? Task.CompletedTask;

private void PreWarmCachesParallel(Block suggestedBlock, Hash256 parentStateRoot, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public partial class BlockProcessor : IBlockProcessor
/// to any block-specific tracers.
/// </summary>
protected BlockReceiptsTracer ReceiptsTracer { get; set; }
private readonly Func<Task, Task> _clearCaches;

public BlockProcessor(
ISpecProvider? specProvider,
Expand Down Expand Up @@ -78,6 +79,7 @@ public BlockProcessor(
_preWarmer = preWarmer;
_beaconBlockRootHandler = new BeaconBlockRootHandler();
ReceiptsTracer = new BlockReceiptsTracer();
_clearCaches = _ => _preWarmer.ClearCachesInBackground();
}

public event EventHandler<BlockProcessedEventArgs>? BlockProcessed;
Expand Down Expand Up @@ -123,6 +125,10 @@ the previous head state.*/
: _preWarmer?.PreWarmCaches(suggestedBlock, preBlockStateRoot!, cancellationTokenSource.Token);
(Block processedBlock, TxReceipt[] receipts) = ProcessOne(suggestedBlock, options, blockTracer);
// Block is processed, we can cancel the prewarm task
if (preWarmTask is not null)
{
preWarmTask = preWarmTask.ContinueWith(_clearCaches).Unwrap();
}
cancellationTokenSource.Cancel();
processedBlocks[i] = processedBlock;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public interface IBlockCachePreWarmer
{
Task PreWarmCaches(Block suggestedBlock, Hash256 parentStateRoot, CancellationToken cancellationToken = default);
void ClearCaches();
Task ClearCachesInBackground();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Nethermind.Core.Test.Collections;

public class LockableConcurrentDictionaryTests
public class ConcurrentDictionaryTests
{
[Test]
public void Locks()
Expand All @@ -28,4 +28,14 @@ public void Locks()
updateTask.Wait();
dictionary.ContainsKey(3).Should().BeTrue();
}

[Test]
public void NoResizeClear()
{
// Tests that the reflection works
ConcurrentDictionary<int, int> dictionary = new(new Dictionary<int, int> { { 0, 0 }, { 1, 1 }, { 2, 2 } });
dictionary.NoResizeClear();

dictionary.Count.Should().Be(0);
}
}
7 changes: 5 additions & 2 deletions src/Nethermind/Nethermind.Core/Caching/ClockCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

using Nethermind.Core.Collections;
using Nethermind.Core.Threading;

using CollectionExtensions = Nethermind.Core.Collections.CollectionExtensions;

namespace Nethermind.Core.Caching;

public sealed class ClockCache<TKey, TValue>(int maxCapacity) : ClockCacheBase<TKey>(maxCapacity)
where TKey : struct, IEquatable<TKey>
{
private readonly ConcurrentDictionary<TKey, LruCacheItem> _cacheMap = new ConcurrentDictionary<TKey, LruCacheItem>();
private readonly ConcurrentDictionary<TKey, LruCacheItem> _cacheMap = new(CollectionExtensions.LockPartitions, maxCapacity);
private readonly McsLock _lock = new();

public TValue Get(TKey key)
Expand Down Expand Up @@ -154,7 +157,7 @@ public bool Delete(TKey key)
using var lockRelease = _lock.Acquire();

base.Clear();
_cacheMap.Clear();
_cacheMap.NoResizeClear();
}

public bool Contains(TKey key)
Expand Down
7 changes: 5 additions & 2 deletions src/Nethermind/Nethermind.Core/Caching/ClockKeyCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

using Nethermind.Core.Collections;
using Nethermind.Core.Threading;

using CollectionExtensions = Nethermind.Core.Collections.CollectionExtensions;

namespace Nethermind.Core.Caching;

public sealed class ClockKeyCache<TKey>(int maxCapacity) : ClockCacheBase<TKey>(maxCapacity)
where TKey : struct, IEquatable<TKey>
{
private readonly ConcurrentDictionary<TKey, int> _cacheMap = new ConcurrentDictionary<TKey, int>();
private readonly ConcurrentDictionary<TKey, int> _cacheMap = new(CollectionExtensions.LockPartitions, maxCapacity);
private readonly McsLock _lock = new();

public bool Get(TKey key)
Expand Down Expand Up @@ -128,7 +131,7 @@ public bool Delete(TKey key)
using var lockRelease = _lock.Acquire();

base.Clear();
_cacheMap.Clear();
_cacheMap.NoResizeClear();
}

public bool Contains(TKey key)
Expand Down
64 changes: 64 additions & 0 deletions src/Nethermind/Nethermind.Core/Collections/CollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace Nethermind.Core.Collections
{
public static class CollectionExtensions
{
public static int LockPartitions { get; } = Environment.ProcessorCount * 16;

public static void AddRange<T>(this ICollection<T> list, IEnumerable<T> items)
{
foreach (T item in items)
Expand All @@ -22,5 +29,62 @@ public static void AddRange<T>(this ICollection<T> list, params T[] items)
list.Add(items[index]);
}
}

public static bool NoResizeClear<TKey, TValue>(this ConcurrentDictionary<TKey, TValue>? dictionary)
where TKey : notnull
{
if (dictionary is null || dictionary.IsEmpty)
{
return false;
}

using var handle = dictionary.AcquireLock();

ClearCache<TKey, TValue>.Clear(dictionary);
return true;
}

private static class ClearCache<TKey, TValue> where TKey : notnull
{
public static readonly Action<ConcurrentDictionary<TKey, TValue>> Clear = CreateNoResizeClearExpression();

private static Action<ConcurrentDictionary<TKey, TValue>> CreateNoResizeClearExpression()
{
// Parameters
var dictionaryParam = Expression.Parameter(typeof(ConcurrentDictionary<TKey, TValue>), "dictionary");

// Access _tables field
var tablesField = typeof(ConcurrentDictionary<TKey, TValue>).GetField("_tables", BindingFlags.NonPublic | BindingFlags.Instance);
var tablesAccess = Expression.Field(dictionaryParam, tablesField!);

// Access _buckets and _countPerLock fields
var tablesType = tablesField!.FieldType;
var bucketsField = tablesType.GetField("_buckets", BindingFlags.NonPublic | BindingFlags.Instance);
var countPerLockField = tablesType.GetField("_countPerLock", BindingFlags.NonPublic | BindingFlags.Instance);

var bucketsAccess = Expression.Field(tablesAccess, bucketsField!);
var countPerLockAccess = Expression.Field(tablesAccess, countPerLockField!);

// Clear arrays using Array.Clear
var clearMethod = typeof(Array).GetMethod("Clear", new[] { typeof(Array), typeof(int), typeof(int) });

var clearBuckets = Expression.Call(clearMethod!,
bucketsAccess,
Expression.Constant(0),
Expression.ArrayLength(bucketsAccess));

var clearCountPerLock = Expression.Call(clearMethod!,
countPerLockAccess,
Expression.Constant(0),
Expression.ArrayLength(countPerLockAccess));

// Block to execute both clears
var block = Expression.Block(clearBuckets, clearCountPerLock);

// Compile the expression into a lambda
return Expression.Lambda<Action<ConcurrentDictionary<TKey, TValue>>>(block, dictionaryParam).Compile();
}
}
}
}

92 changes: 0 additions & 92 deletions src/Nethermind/Nethermind.Core/Resettables/ResettableHashSet.cs

This file was deleted.

3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.State/IWorldState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Threading.Tasks;
using Nethermind.Core;
using Nethermind.Core.Collections;
using Nethermind.Core.Crypto;
Expand Down Expand Up @@ -114,4 +115,6 @@ public interface IWorldState : IJournal<Snapshot>, IReadOnlyStateProvider
ArrayPoolList<AddressAsKey>? GetAccountChanges();

bool ClearCache() => false;

Task ClearCachesInBackground() => Task.CompletedTask;
}
Loading

0 comments on commit 29f2a20

Please sign in to comment.