Skip to content

Commit

Permalink
Reduce allocations in metrics backed by Zero Contention Counter (#7206)
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams authored Jun 21, 2024
1 parent 7ad2a52 commit cd46bf8
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 182 deletions.
109 changes: 109 additions & 0 deletions src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;

namespace Nethermind.Core.Threading;
public class ZeroContentionCounter
{
private ThreadLocal<BoxedLong> _threadLocal = new(() => new BoxedLong(), trackAllValues: true);

private static Func<ThreadLocal<BoxedLong>, long> _totalDelegate = CreateTotalDelegate();

public long GetTotalValue()
{
return _totalDelegate(_threadLocal);
}

private static Func<ThreadLocal<BoxedLong>, long> CreateTotalDelegate()
{
FieldInfo linkedSlot = typeof(ThreadLocal<BoxedLong>).GetField("_linkedSlot", BindingFlags.NonPublic | BindingFlags.Instance)!;
FieldInfo next = linkedSlot.FieldType.GetField("_next", BindingFlags.NonPublic | BindingFlags.Instance)!;
FieldInfo value = next.FieldType.GetField("_value", BindingFlags.NonPublic | BindingFlags.Instance)!;

// The code we are trying to generate:
//
// object? linkedSlot = linkedSlot.GetValue(threadLocal);
// if (linkedSlot == null)
// {
// return 0;
// }
//
// long total = 0;
// for (linkedSlot = next.GetValue(linkedSlot); linkedSlot != null; linkedSlot = next.GetValue(linkedSlot))
// {
// total += (value.GetValue(linkedSlot) as BoxedLong)!.Value;
// }
// return total;

// Parameters
ParameterExpression threadLocalParam = Expression.Parameter(typeof(ThreadLocal<BoxedLong>), "threadLocal");

// Fields
MemberExpression linkedSlotField = Expression.Field(threadLocalParam, linkedSlot);

// Variables
ParameterExpression linkedSlotVar = Expression.Variable(linkedSlotField.Type, "linkedSlot");
ParameterExpression totalVar = Expression.Variable(typeof(long), "total");

// Assignments
BinaryExpression assignLinkedSlot = Expression.Assign(linkedSlotVar, linkedSlotField);
BinaryExpression assignTotal = Expression.Assign(totalVar, Expression.Constant(0L));
BinaryExpression assignNextSlot = Expression.Assign(linkedSlotVar, Expression.Field(linkedSlotVar, next));

// Labels
LabelTarget breakLabel = Expression.Label(typeof(long), "breakLabel");

ConditionalExpression breakCondition =
Expression.IfThen(
Expression.Equal(linkedSlotVar, Expression.Constant(null)),
Expression.Break(breakLabel, totalVar)
);

// Loop body
BlockExpression loopBody = Expression.Block(
breakCondition,
Expression.AddAssign(
totalVar,
Expression.Property(
Expression.Field(linkedSlotVar, value), typeof(BoxedLong),
nameof(BoxedLong.Value)
)
),
assignNextSlot
);

// Loop
LoopExpression loop = Expression.Loop(loopBody);

// Block
BlockExpression block = Expression.Block(
new[] { linkedSlotVar, totalVar },
assignLinkedSlot,
assignTotal,
breakCondition,
assignNextSlot,
loop,
Expression.Label(breakLabel, totalVar)
);

// Lambda
Expression<Func<ThreadLocal<BoxedLong>, long>> lambda =
Expression.Lambda<Func<ThreadLocal<BoxedLong>, long>>(block, threadLocalParam);

return lambda.Compile();
}

public void Increment(int value = 1) => _threadLocal.Value!.Increment(value);
public long ThreadLocalValue => _threadLocal.Value!.Value;

private class BoxedLong
{
private long _value;
public long Value => _value;
public void Increment(int value) => _value += value;
}
}
114 changes: 24 additions & 90 deletions src/Nethermind/Nethermind.Db/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,41 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System.ComponentModel;
using System.Threading;

using Nethermind.Core.Attributes;
using Nethermind.Core.Threading;

namespace Nethermind.Db
{
public static class Metrics
{
[CounterMetric]
[Description("Number of Code DB cache reads.")]
public static long CodeDbCache
{
get
{
long total = 0;
foreach (var value in _codeDbCache.Values)
{
total += value;
}
return total;
}
}
private static readonly ThreadLocal<long> _codeDbCache = new(trackAllValues: true);
public static long CodeDbCache => _codeDbCache.GetTotalValue();
private static ZeroContentionCounter _codeDbCache = new();
[Description("Number of Code DB cache reads on thread.")]
public static long ThreadLocalCodeDbCache => _codeDbCache.Value;
public static void IncrementCodeDbCache() => _codeDbCache.Value++;
public static long ThreadLocalCodeDbCache => _codeDbCache.ThreadLocalValue;
public static void IncrementCodeDbCache() => _codeDbCache.Increment();

[CounterMetric]
[Description("Number of State Trie cache hits.")]
public static long StateTreeCache
{
get
{
long total = 0;
foreach (var value in _stateTreeCacheHits.Values)
{
total += value;
}
return total;
}
}

private static readonly ThreadLocal<long> _stateTreeCacheHits = new(trackAllValues: true);
public static void IncrementStateTreeCacheHits() => _stateTreeCacheHits.Value++;
public static long StateTreeCache => _stateTreeCacheHits.GetTotalValue();
private static ZeroContentionCounter _stateTreeCacheHits = new();
public static void IncrementStateTreeCacheHits() => _stateTreeCacheHits.Increment();

[CounterMetric]
[Description("Number of State Trie reads.")]
public static long StateTreeReads
{
get
{
long total = 0;
foreach (var value in _stateTreeReads.Values)
{
total += value;
}
return total;
}
}
private static readonly ThreadLocal<long> _stateTreeReads = new(trackAllValues: true);
public static long StateTreeReads => _stateTreeReads.GetTotalValue();
private static ZeroContentionCounter _stateTreeReads = new();

[Description("Number of State Trie reads on thread.")]
public static long ThreadLocalStateTreeReads => _stateTreeReads.Value;
public static void IncrementStateTreeReads() => _stateTreeReads.Value++;
public static long ThreadLocalStateTreeReads => _stateTreeReads.ThreadLocalValue;
public static void IncrementStateTreeReads() => _stateTreeReads.Increment();

[CounterMetric]
[Description("Number of State Reader reads.")]
public static long StateReaderReads
{
get
{
long total = 0;
foreach (var value in _stateReaderReads.Values)
{
total += value;
}
return total;
}
}
private static readonly ThreadLocal<long> _stateReaderReads = new(trackAllValues: true);
public static void IncrementStateReaderReads() => _stateReaderReads.Value++;
public static long StateReaderReads => _stateReaderReads.GetTotalValue();
private static ZeroContentionCounter _stateReaderReads = new();
public static void IncrementStateReaderReads() => _stateReaderReads.Increment();

[CounterMetric]
[Description("Number of Blocks Trie writes.")]
Expand All @@ -93,39 +48,18 @@ public static long StateReaderReads

[CounterMetric]
[Description("Number of storage trie cache hits.")]
public static long StorageTreeCache
{
get
{
long total = 0;
foreach (var value in _storageTreeCache.Values)
{
total += value;
}
return total;
}
}
private static readonly ThreadLocal<long> _storageTreeCache = new(trackAllValues: true);
public static void IncrementStorageTreeCache() => _storageTreeCache.Value++;
public static long StorageTreeCache => _storageTreeCache.GetTotalValue();
private static ZeroContentionCounter _storageTreeCache = new();
public static void IncrementStorageTreeCache() => _storageTreeCache.Increment();

[CounterMetric]
[Description("Number of storage trie reads.")]
public static long StorageTreeReads
{
get
{
long total = 0;
foreach (var value in _storageTreeReads.Values)
{
total += value;
}
return total;
}
}
private static readonly ThreadLocal<long> _storageTreeReads = new(trackAllValues: true);
public static long StorageTreeReads => _storageTreeReads.GetTotalValue();
private static ZeroContentionCounter _storageTreeReads = new();

[Description("Number of storage trie reads on thread.")]
public static long ThreadLocalStorageTreeReads => _storageTreeReads.Value;
public static void IncrementStorageTreeReads() => _storageTreeReads.Value++;
public static long ThreadLocalStorageTreeReads => _storageTreeReads.ThreadLocalValue;
public static void IncrementStorageTreeReads() => _storageTreeReads.Increment();

[CounterMetric]
[Description("Number of storage reader reads.")]
Expand Down
Loading

0 comments on commit cd46bf8

Please sign in to comment.