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

Reduce allocations in metrics backed by Zero Contention Counter #7206

Merged
merged 4 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
94 changes: 94 additions & 0 deletions src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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)!;

// 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