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

Wait for background JumpDestinationAnalysis if in progress #7008

Merged
merged 13 commits into from
May 14, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,9 @@ private void FireProcessingQueueEmpty()

if (!readonlyChain)
{
_stats.UpdateStats(lastProcessed, _blockTree, _recoveryQueue.Count, _blockQueue.Count, _stopwatch.ElapsedMicroseconds());
Metrics.RecoveryQueueSize = _recoveryQueue.Count;
Metrics.ProcessingQueueSize = _blockQueue.Count;
_stats.UpdateStats(lastProcessed, _blockTree, _stopwatch.ElapsedMicroseconds());
}

return lastProcessed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ internal class ProcessingStats
private long _processingMicroseconds;
private long _lastTotalCreates;
private long _lastReportMs;
private long _lastContractsAnalysed;
private long _lastCachedContractsUsed;

public ProcessingStats(ILogger logger)
{
Expand All @@ -51,7 +53,7 @@ public ProcessingStats(ILogger logger)
#endif
}

public void UpdateStats(Block? block, IBlockTree blockTreeCtx, int recoveryQueueSize, int blockQueueSize, long blockProcessingTimeInMicros)
public void UpdateStats(Block? block, IBlockTree blockTreeCtx, long blockProcessingTimeInMicros)
{
const string resetColor = "\u001b[37m";
const string whiteText = "\u001b[97m";
Expand Down Expand Up @@ -85,8 +87,6 @@ public void UpdateStats(Block? block, IBlockTree blockTreeCtx, int recoveryQueue
Metrics.LastDifficulty = block.Difficulty;
Metrics.GasUsed = block.GasUsed;
Metrics.GasLimit = block.GasLimit;
Metrics.RecoveryQueueSize = recoveryQueueSize;
Metrics.ProcessingQueueSize = blockQueueSize;

Metrics.BlockchainHeight = block.Header.Number;
Metrics.BestKnownBlockNumber = blockTreeCtx.BestKnownNumber;
Expand Down Expand Up @@ -117,6 +117,8 @@ public void UpdateStats(Block? block, IBlockTree blockTreeCtx, int recoveryQueue
long chunkCreates = Evm.Metrics.Creates - _lastTotalCreates;
long chunkSload = Evm.Metrics.SloadOpcode - _lastTotalSLoad;
long chunkSstore = Evm.Metrics.SstoreOpcode - _lastTotalSStore;
long contractsAnalysed = Evm.Metrics.ContractsAnalysed - _lastContractsAnalysed;
long cachedContractsUsed = Db.Metrics.CodeDbCache - _lastCachedContractsUsed;
double chunkMGas = Metrics.Mgas - _lastTotalMGas;
double mgasPerSecond = chunkMicroseconds == 0 ? -1 : chunkMGas / chunkMicroseconds * 1_000_000.0;
double totalMgasPerSecond = totalMicroseconds == 0 ? -1 : Metrics.Mgas / totalMicroseconds * 1_000_000.0;
Expand Down Expand Up @@ -201,7 +203,7 @@ public void UpdateStats(Block? block, IBlockTree blockTreeCtx, int recoveryQueue
_ => ""
};
_logger.Info($"- Block{(chunkBlocks > 1 ? $"s {chunkBlocks,-9:N0}" : " ")}{(chunkBlocks == 1 ? mgasColor : "")} {chunkMGas,7:F2}{resetColor} MGas | {chunkTx,6:N0} txs | calls {callsColor}{chunkCalls,6:N0}{resetColor} {darkGreyText}({chunkEmptyCalls,3:N0}){resetColor} | sload {chunkSload,7:N0} | sstore {sstoreColor}{chunkSstore,6:N0}{resetColor} | create {createsColor}{chunkCreates,3:N0}{resetColor}{(currentSelfDestructs - _lastSelfDestructs > 0 ? $"{darkGreyText}({-(currentSelfDestructs - _lastSelfDestructs),3:N0}){resetColor}" : "")}");
_logger.Info($"- Block throughput {mgasPerSecondColor}{mgasPerSecond,7:F2}{resetColor} MGas/s | {txps,9:F2} t/s | {bps,7:F2} Blk/s | recv {recoveryQueueSize,7:N0} | proc {blockQueueSize,6:N0}");
_logger.Info($"- Block throughput {mgasPerSecondColor}{mgasPerSecond,7:F2}{resetColor} MGas/s | {txps,9:F2} t/s | {bps,7:F2} Blk/s | scontracts{resetColor} from cache {cachedContractsUsed,7:N0} |{resetColor} analysed {contractsAnalysed,5:N0}");
// Only output the total throughput in debug mode
if (_logger.IsDebug)
{
Expand All @@ -223,6 +225,8 @@ public void UpdateStats(Block? block, IBlockTree blockTreeCtx, int recoveryQueue

}

_lastCachedContractsUsed = Db.Metrics.CodeDbCache;
_lastContractsAnalysed = Evm.Metrics.ContractsAnalysed;
_lastReportMs = reportMs;
_lastBlockNumber = Metrics.Blocks;
_lastTotalMGas = Metrics.Mgas;
Expand Down
8 changes: 8 additions & 0 deletions src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,13 @@ void IThreadPoolWorkItem.Execute()
{
_analyzer.Execute();
}

public void AnalyseInBackgroundIfRequired()
{
if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && _analyzer.RequiresAnalysis)
{
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Threading;

namespace Nethermind.Evm.CodeAnalysis
{
Expand All @@ -19,17 +20,56 @@ public sealed class JumpDestinationAnalyzer(ReadOnlyMemory<byte> code)
private static readonly long[]? _emptyJumpDestinationBitmap = new long[1];
private long[]? _jumpDestinationBitmap = code.Length == 0 ? _emptyJumpDestinationBitmap : null;

private object? _analysisComplete;
private ReadOnlyMemory<byte> MachineCode { get; } = code;

public bool ValidateJump(int destination)
{
ReadOnlySpan<byte> machineCode = MachineCode.Span;
_jumpDestinationBitmap ??= CreateJumpDestinationBitmap(machineCode);
_jumpDestinationBitmap ??= CreateOrWaitForJumpDestinationBitmap();

// Cast to uint to change negative numbers to very int high numbers
// Then do length check, this both reduces check by 1 and eliminates the bounds
// check from accessing the span.
return (uint)destination < (uint)machineCode.Length && IsJumpDestination(_jumpDestinationBitmap, destination);
return (uint)destination < (uint)MachineCode.Length && IsJumpDestination(_jumpDestinationBitmap, destination);
}

private long[] CreateOrWaitForJumpDestinationBitmap()
{
object? previous = Volatile.Read(ref _analysisComplete);
if (previous is null)
{
ManualResetEventSlim analysisComplete = new(initialState: false);
previous = Interlocked.CompareExchange(ref _analysisComplete, analysisComplete, null);
if (previous is null)
{
// Not already in progress, so start it.
var bitmap = CreateJumpDestinationBitmap();
_jumpDestinationBitmap = bitmap;
// Release the MRES to be GC'd
benaadams marked this conversation as resolved.
Show resolved Hide resolved
_analysisComplete = bitmap;
// Signal complete.
analysisComplete.Set();
return bitmap;
}
benaadams marked this conversation as resolved.
Show resolved Hide resolved
}

if (previous is ManualResetEventSlim resetEvent)
{
Thread thread = Thread.CurrentThread;
ThreadPriority priority = thread.Priority;
// We are waiting, so drop priority to normal (BlockProcessing runs at higher priority).
thread.Priority = ThreadPriority.Normal;

// Already in progress, wait for completion.
resetEvent.Wait();

// Restore the priority of the thread.
thread.Priority = priority;
benaadams marked this conversation as resolved.
Show resolved Hide resolved
return _jumpDestinationBitmap;
}

// Must be the bitmap, and lost check->create benign data race
return (long[])previous;
}

/// <summary>
Expand All @@ -55,8 +95,10 @@ private static int GetInt64ArrayLengthFromBitLength(int n) =>
/// Collects data locations in code.
/// An unset bit means the byte is an opcode, a set bit means it's data.
/// </summary>
private static long[] CreateJumpDestinationBitmap(ReadOnlySpan<byte> code)
private long[] CreateJumpDestinationBitmap()
{
Metrics.IncrementContractsAnalysed();
ReadOnlySpan<byte> code = MachineCode.Span;
long[] jumpDestinationBitmap = new long[GetInt64ArrayLengthFromBitLength(code.Length)];
int programCounter = 0;
// We accumulate each array segment to a register and then flush to memory when we move to next.
Expand Down Expand Up @@ -146,6 +188,7 @@ private static long[] CreateJumpDestinationBitmap(ReadOnlySpan<byte> code)
/// <summary>
/// Checks if the position is in a code segment.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsJumpDestination(long[] bitvec, int pos)
{
int vecIndex = pos >> BitShiftPerInt64;
Expand All @@ -164,8 +207,28 @@ private static void MarkJumpDestinations(long[] jumpDestinationBitmap, int pos,

public void Execute()
{
// This is to support background thread preparation of the bitmap.
_jumpDestinationBitmap ??= CreateJumpDestinationBitmap(MachineCode.Span);
if (_jumpDestinationBitmap is null && Volatile.Read(ref _analysisComplete) is null)
{
ManualResetEventSlim analysisComplete = new(initialState: false);
if (Interlocked.CompareExchange(ref _analysisComplete, analysisComplete, null) is null)
{
Thread thread = Thread.CurrentThread;
ThreadPriority priority = thread.Priority;
// Boost the priority of the thread as block processing may be waiting on this.
thread.Priority = ThreadPriority.AboveNormal;

_jumpDestinationBitmap ??= CreateJumpDestinationBitmap();

// Release the MRES to be GC'd
_analysisComplete = _jumpDestinationBitmap;
// Signal complete.
analysisComplete.Set();
// Restore the priority of the thread.
thread.Priority = priority;
benaadams marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

public bool RequiresAnalysis => _jumpDestinationBitmap is null;
}
}
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Evm/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;

using Nethermind.Core.Attributes;

Expand Down Expand Up @@ -77,6 +78,11 @@ public class Metrics
[CounterMetric]
[Description("Number of contract create calls.")]
public static long Creates { get; set; }
private static long _contractsAnalysed;
[Description("Number of contracts' code analysed for jump destinations.")]
public static long ContractsAnalysed => _contractsAnalysed;
public static void IncrementContractsAnalysed() => Interlocked.Increment(ref _contractsAnalysed);

internal static long Transactions { get; set; }
internal static float AveGasPrice { get; set; }
internal static float MinGasPrice { get; set; } = float.MaxValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ protected ExecutionEnvironment BuildExecutionEnvironment(
? new(tx.Data ?? Memory<byte>.Empty)
: VirtualMachine.GetCachedCodeInfo(WorldState, recipient, spec);

codeInfo.AnalyseInBackgroundIfRequired();

byte[] inputData = tx.IsMessageCall ? tx.Data.AsArray() ?? Array.Empty<byte>() : Array.Empty<byte>();

return new ExecutionEnvironment
Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Evm/VirtualMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,7 @@ public TransactionSubstate Run<TTracingActions>(EvmState state, IWorldState worl
public void InsertCode(ReadOnlyMemory<byte> code, Address callCodeOwner, IReleaseSpec spec)
{
var codeInfo = new CodeInfo(code);
// Start generating the JumpDestinationBitmap in background.
ThreadPool.UnsafeQueueUserWorkItem(codeInfo, preferLocal: false);
codeInfo.AnalyseInBackgroundIfRequired();

Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span);
_state.InsertCode(callCodeOwner, codeHash, code, spec);
Expand Down Expand Up @@ -2492,6 +2491,7 @@ private EvmExceptionType InstructionSelfDestruct<TTracing>(EvmState vmState, ref
// pointing to data in this tx and will become invalid
// for another tx as returned to pool.
CodeInfo codeInfo = new(initCode);
codeInfo.AnalyseInBackgroundIfRequired();

ExecutionEnvironment callEnv = new
(
Expand Down