From 21d2c2bad4c426cf83c88748e052746edfa9a6df Mon Sep 17 00:00:00 2001 From: Amirul Ashraf Date: Tue, 2 Apr 2024 18:16:39 +0800 Subject: [PATCH] Add benchmark (#6888) --- .../Ethereum.Test.Base/BlockchainTestBase.cs | 1 + .../Verkle/VerkleWorldStateBenchmark.cs | 305 ++++++++++++++++++ .../Nethermind.Evm.Benchmark/EvmBenchmarks.cs | 2 + .../MultipleUnsignedOperations.cs | 2 + .../StaticCallBenchmarks.cs | 2 + .../EthModuleBenchmarks.cs | 12 +- 6 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 src/Nethermind/Nethermind.Benchmark/Verkle/VerkleWorldStateBenchmark.cs diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index fe7fd623b4c5..8b6601c80401 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -166,6 +166,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? stateProvider, receiptStorage, NullWitnessCollector.Instance, + blockTree, _logManager); IBlockchainProcessor blockchainProcessor = new BlockchainProcessor( diff --git a/src/Nethermind/Nethermind.Benchmark/Verkle/VerkleWorldStateBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Verkle/VerkleWorldStateBenchmark.cs new file mode 100644 index 000000000000..908c4657a1d8 --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Verkle/VerkleWorldStateBenchmark.cs @@ -0,0 +1,305 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using MathNet.Numerics.Random; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.Specs.Forks; +using Nethermind.State; +using Nethermind.Trie.Pruning; +using Nethermind.Verkle.Tree.TreeStore; + +namespace Nethermind.Benchmarks.Verkle; + +public class VerkleWorldStateBenchmark +{ + + [Params(TreeTypeEnum.Verkle, TreeTypeEnum.MPT)] + public TreeTypeEnum TreeType { get; set; } + + [Params( + ConfigurationEnum.Balance, + ConfigurationEnum.WriteHeavy, + ConfigurationEnum.ReadHeavy, + ConfigurationEnum.AddressOnly + )] + public ConfigurationEnum Configuration { get; set; } + + private AccountDecoder _accountDecoder = new(); + + private const int _largerEntryCount = 1024 * 10; + private (Op, Address, UInt256?, byte[])[] _largerEntriesAccess; + + enum Op + { + Read, + Write + } + + public enum TreeTypeEnum + { + MPT, + Verkle + } + + public enum ConfigurationEnum + { + Balance, + WriteHeavy, + ReadHeavy, + AddressOnly + } + + [GlobalSetup] + public void Setup() + { + double storageAccessProbability; + double storageExistingReadProbability; + double storageExistingWriteProbability; + double addressExistingReadProbability; + double addressExistingWriteProbability; + + switch (Configuration) + { + case ConfigurationEnum.Balance: + storageAccessProbability = 0.9; + storageExistingReadProbability = 0.3; + storageExistingWriteProbability = 0.3; + addressExistingReadProbability = 0.3; + addressExistingWriteProbability = 0.3; + break; + case ConfigurationEnum.WriteHeavy: + storageAccessProbability = 0.9; + storageExistingReadProbability = 0.1; + storageExistingWriteProbability = 0.5; + addressExistingReadProbability = 0.1; + addressExistingWriteProbability = 0.5; + break; + case ConfigurationEnum.ReadHeavy: + storageAccessProbability = 0.9; + storageExistingReadProbability = 0.9; + storageExistingWriteProbability = 0.1; + addressExistingReadProbability = 0.9; + addressExistingWriteProbability = 0.1; + break; + case ConfigurationEnum.AddressOnly: + storageAccessProbability = 0.0; + storageExistingReadProbability = 0.0; + storageExistingWriteProbability = 0.0; + addressExistingReadProbability = 0.3; + addressExistingWriteProbability = 0.3; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + // Preparing access for large entries + List
currentAddress = new(); + Dictionary> currentSlots = new(); + Dictionary> storageAccessHistory = new(); + _largerEntriesAccess = new (Op, Address, UInt256?, byte[])[_largerEntryCount]; + + Random rand = new Random(0); + for (int i = 0; i < _largerEntryCount; i++) + { + if (rand.NextDouble() < storageAccessProbability && currentAddress.Count != 0) + { + double prob = rand.NextDouble(); + Address address = currentAddress[(int)(rand.NextInt64() % currentAddress.Count)]; + if (!currentSlots.TryGetValue(address, out List currentSlotsForAddress)) + { + currentSlotsForAddress = new List(); + currentSlots[address] = currentSlotsForAddress; + } + if (!storageAccessHistory.TryGetValue(address, out List accessHistory)) + { + accessHistory = new List(); + storageAccessHistory[address] = accessHistory; + } + + if (prob < storageExistingReadProbability && currentSlotsForAddress.Count != 0) + { + // Its an existing read + UInt256 slot = DetermineSlotToAccess(accessHistory, rand); + + accessHistory.Add(slot); + _largerEntriesAccess[i] = ( + Op.Read, + address, + slot, + null); + } + else if (prob < storageExistingWriteProbability && currentSlotsForAddress.Count != 0) + { + UInt256 slot = DetermineSlotToAccess(accessHistory, rand); + + accessHistory.Add(slot); + _largerEntriesAccess[i] = ( + Op.Write, + address, + slot, + Keccak.Compute(i.ToBigEndianByteArray()).BytesToArray()); + } + else + { + // Its a new write + // Uses the total slot address as the address which causes the storage to be dense. + // Which is not really, realistic, but need to somehow make the slots dense as verkle is somewhat + // optimized for that. + UInt256 newSlot = new UInt256((ulong)currentSlotsForAddress.Count); + accessHistory.Add(newSlot); + + currentSlotsForAddress.Add(newSlot); + _largerEntriesAccess[i] = ( + Op.Write, + address, + newSlot, + Keccak.Compute(i.ToBigEndianByteArray()).BytesToArray()); + } + } + else + { + double prob = rand.NextDouble(); + if (prob < addressExistingReadProbability && currentAddress.Count != 0) + { + // Its an existing read + _largerEntriesAccess[i] = ( + Op.Read, + currentAddress[(int)(rand.NextInt64() % currentAddress.Count)], + null, + null); + } + else if (prob < addressExistingWriteProbability && currentAddress.Count != 0) + { + // Its an existing write + Address addr = currentAddress[(int)(rand.NextInt64() % currentAddress.Count)]; + if (currentSlots.ContainsKey(addr)) + { + // Can't change account of an address with slots as it will break storage root. + i--; + continue; + } + + Account newAccount = new Account((UInt256)rand.NextInt64(), (UInt256)rand.NextInt64()); + _largerEntriesAccess[i] = ( + Op.Write, + addr, + null, + _accountDecoder.Encode(newAccount).Bytes); + } + else + { + // Its a new write + Address newAddress = new Address(Keccak.Compute(i.ToBigEndianByteArray())); + Account newAccount = new Account((UInt256)rand.NextInt64(), (UInt256)rand.NextInt64()); + currentAddress.Add(newAddress); + _largerEntriesAccess[i] = ( + Op.Write, + newAddress, + null, + _accountDecoder.Encode(newAccount).Bytes); + } + } + } + } + + private static UInt256 DetermineSlotToAccess(List accessHistory, Random rand) + { + // Try to get a somewhat recently accessed slot. Verkle is optimized for this kind of scenario. + // It has no effect on MPT. Obviously, I'm just winging it here. + + double fallbackProb = 0.1; // But sometimes, we just wanna break the pattern a bit. + + int idx = accessHistory.Count - (int)Math.Abs(SampleGaussian(rand, 0, Math.Max(accessHistory.Count * 0.1, 50))); + if (idx >= accessHistory.Count || idx < 0 || rand.NextDouble() < fallbackProb) idx = (int)(rand.NextInt64() % accessHistory.Count); + UInt256 slot = accessHistory[idx]; + return slot; + } + + // Copied from https://gist.github.com/tansey/1444070 + public static double SampleGaussian(Random random, double mean, double stddev) + { + // The method requires sampling from a uniform random of (0,1] + // but Random.NextDouble() returns a sample of [0,1). + double x1 = 1 - random.NextDouble(); + double x2 = 1 - random.NextDouble(); + + double y1 = Math.Sqrt(-2.0 * Math.Log(x1)) * Math.Cos(2.0 * Math.PI * x2); + return y1 * stddev + mean; + } + + private IWorldState CreateVerkleWorldState() + { + IDbProvider dbProvider = new DbProvider(); + dbProvider.RegisterColumnDb(DbNames.VerkleState, new MemColumnsDb()); + dbProvider.RegisterDb(DbNames.StateRootToBlock, new MemDb()); + dbProvider.RegisterDb(DbNames.Code, new MemDb()); + var LogManager = NullLogManager.Instance; + var TrieStore = new VerkleTreeStore(dbProvider, LogManager); + var State = new VerkleWorldState(TrieStore, dbProvider.CodeDb, LogManager); + + State.Commit(Prague.Instance); + State.CommitTree(0); + return State; + } + + private IWorldState CreatePMTWorldState() + { + TrieStore trieStore = new TrieStore(new MemDb(), NullLogManager.Instance); + return new WorldState(trieStore, new MemDb(), NullLogManager.Instance); + } + + private IWorldState CreateWorldState() + { + return TreeType == TreeTypeEnum.Verkle ? CreateVerkleWorldState() : CreatePMTWorldState(); + } + + [Benchmark] + public void InsertAndCommitRepeatedlyTimes() + { + IWorldState worldState = CreateWorldState(); + + for (int i = 0; i < _largerEntryCount; i++) + { + if (i % 2000 == 0) + { + worldState.Commit(Prague.Instance); + worldState.CommitTree(i / 2000); + } + + (Op op, Address address, UInt256? slot, byte[] value) = _largerEntriesAccess[i]; + + if (op == Op.Write) + { + if (slot == null) + { + Account asAccount = _accountDecoder.Decode(value); + worldState.CreateAccount(address, asAccount.Balance, asAccount.Nonce); + } + else + { + worldState.Set(new StorageCell(address, slot.Value), value); + } + } + else + { + if (slot == null) + { + worldState.GetAccount(address); + } + else + { + worldState.Get(new StorageCell(address, slot.Value)); + } + } + } + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs index 5e4901869924..67c73a0482d5 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs @@ -11,6 +11,7 @@ using Nethermind.Evm.CodeAnalysis; using Nethermind.Specs; using Nethermind.Evm.Tracing; +using Nethermind.Evm.Witness; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.State; @@ -56,6 +57,7 @@ public void GlobalSetup() value: 0, transferValue: 0, txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + witness: new NoExecWitness(), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs index d3d8889bcc7d..7eaaf7fc1d52 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs @@ -11,6 +11,7 @@ using Nethermind.Db; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; +using Nethermind.Evm.Witness; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs; @@ -87,6 +88,7 @@ public void GlobalSetup() value: 0, transferValue: 0, txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + witness: new NoExecWitness(), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs index 93e158881a79..4e00be507429 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs @@ -12,6 +12,7 @@ using Nethermind.Evm.CodeAnalysis; using Nethermind.Specs; using Nethermind.Evm.Tracing; +using Nethermind.Evm.Witness; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs.Forks; @@ -98,6 +99,7 @@ public void GlobalSetup() value: 0, transferValue: 0, txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + witness: new NoExecWitness(), inputData: default ); diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index 78641dbe1edd..a343757be684 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -92,8 +92,16 @@ TransactionProcessor transactionProcessor = new(MainnetSpecProvider.Instance, stateProvider, _virtualMachine, LimboLogs.Instance); IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor = new BlockProcessor.BlockValidationTransactionsExecutor(transactionProcessor, stateProvider); - BlockProcessor blockProcessor = new(specProvider, Always.Valid, new RewardCalculator(specProvider), transactionsExecutor, - stateProvider, NullReceiptStorage.Instance, NullWitnessCollector.Instance, LimboLogs.Instance); + BlockProcessor blockProcessor = new( + specProvider, + Always.Valid, + new RewardCalculator(specProvider), + transactionsExecutor, + stateProvider, + NullReceiptStorage.Instance, + NullWitnessCollector.Instance, + blockTree, + LimboLogs.Instance); EthereumEcdsa ecdsa = new(specProvider.ChainId, LimboLogs.Instance); BlockchainProcessor blockchainProcessor = new(