diff --git a/src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs b/src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs new file mode 100644 index 00000000000..6ed9cb9aae0 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs @@ -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 _threadLocal = new(() => new BoxedLong(), trackAllValues: true); + + private static Func, long> _totalDelegate = CreateTotalDelegate(); + + public long GetTotalValue() + { + return _totalDelegate(_threadLocal); + } + + private static Func, long> CreateTotalDelegate() + { + FieldInfo linkedSlot = typeof(ThreadLocal).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), "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, long>> lambda = + Expression.Lambda, 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; + } +} diff --git a/src/Nethermind/Nethermind.Db/Metrics.cs b/src/Nethermind/Nethermind.Db/Metrics.cs index c71eda3f8b7..ba26f0722b9 100644 --- a/src/Nethermind/Nethermind.Db/Metrics.cs +++ b/src/Nethermind/Nethermind.Db/Metrics.cs @@ -2,9 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.ComponentModel; -using System.Threading; - using Nethermind.Core.Attributes; +using Nethermind.Core.Threading; namespace Nethermind.Db { @@ -12,76 +11,32 @@ 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 _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 _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 _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 _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.")] @@ -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 _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 _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.")] diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs index afb90673720..c4efd23b75a 100644 --- a/src/Nethermind/Nethermind.Evm/Metrics.cs +++ b/src/Nethermind/Nethermind.Evm/Metrics.cs @@ -3,8 +3,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -using System.Threading; - +using Nethermind.Core.Threading; using Nethermind.Core.Attributes; [assembly: InternalsVisibleTo("Nethermind.Consensus")] @@ -22,60 +21,27 @@ public class Metrics [CounterMetric] [Description("Number of calls to other contracts.")] - public static long Calls - { - get - { - long total = 0; - foreach (var value in _calls.Values) - { - total += value; - } - return total; - } - } - private static readonly ThreadLocal _calls = new(trackAllValues: true); + public static long Calls => _calls.GetTotalValue(); + private static ZeroContentionCounter _calls = new(); [Description("Number of calls to other contracts on thread.")] - public static long ThreadLocalCalls => _calls.Value; - public static void IncrementCalls() => _calls.Value++; + public static long ThreadLocalCalls => _calls.ThreadLocalValue; + public static void IncrementCalls() => _calls.Increment(); [CounterMetric] [Description("Number of SLOAD opcodes executed.")] - public static long SloadOpcode - { - get - { - long total = 0; - foreach (var value in _sLoadOpcode.Values) - { - total += value; - } - return total; - } - } - private static readonly ThreadLocal _sLoadOpcode = new(trackAllValues: true); + public static long SloadOpcode => _sLoadOpcode.GetTotalValue(); + private static ZeroContentionCounter _sLoadOpcode = new(); [Description("Number of SLOAD opcodes executed on thread.")] - public static long ThreadLocalSLoadOpcode => _sLoadOpcode.Value; - public static void IncrementSLoadOpcode() => _sLoadOpcode.Value++; + public static long ThreadLocalSLoadOpcode => _sLoadOpcode.ThreadLocalValue; + public static void IncrementSLoadOpcode() => _sLoadOpcode.Increment(); [CounterMetric] [Description("Number of SSTORE opcodes executed.")] - public static long SstoreOpcode - { - get - { - long total = 0; - foreach (var value in _sStoreOpcode.Values) - { - total += value; - } - return total; - } - } - private static readonly ThreadLocal _sStoreOpcode = new(trackAllValues: true); + public static long SstoreOpcode => _sStoreOpcode.GetTotalValue(); + private static ZeroContentionCounter _sStoreOpcode = new(); [Description("Number of SSTORE opcodes executed on thread.")] - public static long ThreadLocalSStoreOpcode => _sStoreOpcode.Value; - public static void IncrementSStoreOpcode() => _sStoreOpcode.Value++; + public static long ThreadLocalSStoreOpcode => _sStoreOpcode.ThreadLocalValue; + public static void IncrementSStoreOpcode() => _sStoreOpcode.Increment(); [Description("Number of TLOAD opcodes executed.")] public static long TloadOpcode { get; set; } @@ -121,59 +87,27 @@ public static long SstoreOpcode [CounterMetric] [Description("Number of calls made to addresses without code.")] - public static long EmptyCalls - { - get - { - long total = 0; - foreach (var value in _emptyCalls.Values) - { - total += value; - } - return total; - } - } - private static readonly ThreadLocal _emptyCalls = new(trackAllValues: true); + public static long EmptyCalls => _emptyCalls.GetTotalValue(); + private static ZeroContentionCounter _emptyCalls = new(); [Description("Number of calls made to addresses without code on thread.")] - public static long ThreadLocalEmptyCalls => _emptyCalls.Value; - public static void IncrementEmptyCalls() => _emptyCalls.Value++; + public static long ThreadLocalEmptyCalls => _emptyCalls.ThreadLocalValue; + public static void IncrementEmptyCalls() => _emptyCalls.Increment(); [CounterMetric] [Description("Number of contract create calls.")] - public static long Creates - { - get - { - long total = 0; - foreach (var value in _creates.Values) - { - total += value; - } - return total; - } - } - private static readonly ThreadLocal _creates = new(trackAllValues: true); + public static long Creates => _creates.GetTotalValue(); + + private static ZeroContentionCounter _creates = new(); [Description("Number of contract create calls on thread.")] - public static long ThreadLocalCreates => _creates.Value; - public static void IncrementCreates() => _creates.Value++; + public static long ThreadLocalCreates => _creates.ThreadLocalValue; + public static void IncrementCreates() => _creates.Increment(); [Description("Number of contracts' code analysed for jump destinations.")] - public static long ContractsAnalysed - { - get - { - long total = 0; - foreach (var value in _contractsAnalysed.Values) - { - total += value; - } - return total; - } - } - private static readonly ThreadLocal _contractsAnalysed = new(trackAllValues: true); + public static long ContractsAnalysed => _contractsAnalysed.GetTotalValue(); + private static ZeroContentionCounter _contractsAnalysed = new(); [Description("Number of contracts' code analysed for jump destinations on thread.")] - public static long ThreadLocalContractsAnalysed => _contractsAnalysed.Value; - public static void IncrementContractsAnalysed() => _contractsAnalysed.Value++; + public static long ThreadLocalContractsAnalysed => _contractsAnalysed.ThreadLocalValue; + public static void IncrementContractsAnalysed() => _contractsAnalysed.Increment(); internal static long Transactions { get; set; } internal static float AveGasPrice { get; set; }