Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Add benchmark for BlockHashOperation #203

Merged
merged 4 commits into from
Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected BlockHeader createFinalBlockHeader(final SealableBlockHeader sealableB
} catch (final InterruptedException ex) {
throw new CancellationException();
} catch (final ExecutionException ex) {
throw new RuntimeException("Failure occurred during nonce calculations.");
throw new RuntimeException("Failure occurred during nonce calculations.", ex);
}
return BlockHeaderBuilder.create()
.populateFrom(sealableBlockHeader)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolver;
import tech.pegasys.pantheon.ethereum.mainnet.EthHasher.Light;
import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationTestUtils;
import tech.pegasys.pantheon.util.bytes.BytesValue;

Expand All @@ -41,7 +42,9 @@ public class EthHashBlockCreatorTest {
BytesValue.fromHexString("0x476574682f76312e302e302f6c696e75782f676f312e342e32");

private final ExecutionContextTestFixture executionContextTestFixture =
new ExecutionContextTestFixture();
ExecutionContextTestFixture.builder()
.protocolSchedule(MainnetProtocolSchedule.create(2, 3, 10, 11, 12, -1, 42))
.build();

@Test
public void createMainnetBlock1() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

public class EthHashMiningCoordinatorTest {

private final ExecutionContextTestFixture executionContext = new ExecutionContextTestFixture();
private final ExecutionContextTestFixture executionContext = ExecutionContextTestFixture.create();
private final SyncState syncState = mock(SyncState.class);
private final EthHashMinerExecutor executor = mock(EthHashMinerExecutor.class);
private final EthHashBlockMiner miner = mock(EthHashBlockMiner.class);
Expand Down
9 changes: 9 additions & 0 deletions ethereum/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ dependencies {
testSupportImplementation 'org.assertj:assertj-core'
testSupportImplementation 'org.mockito:mockito-core'
testSupportImplementation 'junit:junit'

jmhImplementation project(':util')
jmhImplementation project( path: ':ethereum:core', configuration: 'testSupportArtifacts')
jmhImplementation project(':crypto')
jmhImplementation project(':ethereum:rlp')
jmhImplementation project(':ethereum:trie')
jmhImplementation project(':services:kvstore')
jmhImplementation 'com.google.guava:guava'
jmhImplementation 'org.openjdk.jmh:jmh-generator-annprocess'
}

configurations { testArtifacts }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.ethereum.vm.operations;

import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator;
import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.uint.UInt256;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;

@State(Scope.Thread)
public class BlockHashOperationBenchmark {

@Param({
"1", // Worst-case scenario
"125", // Must iterate up the chain
"255" // Hash available directly via current header's parentHash
})
public long blockNumber;

private OperationBenchmarkHelper operationBenchmarkHelper;
private BlockHashOperation operation;
private MessageFrame frame;

@Setup
public void prepare() throws Exception {
operationBenchmarkHelper = OperationBenchmarkHelper.create();
operation = new BlockHashOperation(new ConstantinopleGasCalculator());
frame = operationBenchmarkHelper.createMessageFrame();
}

@TearDown
public void cleanUp() throws Exception {
operationBenchmarkHelper.cleanUp();
}

@Benchmark
public Bytes32 executeOperation() {
frame.pushStackItem(UInt256.of(blockNumber).getBytes());
operation.execute(frame);
return frame.popStackItem();
}

@Benchmark
public Bytes32 executeOperationWithEmptyHashCache() {
final MessageFrame cleanFrame =
operationBenchmarkHelper
.createMessageFrameBuilder()
.blockHashLookup(new BlockHashLookup(frame.getBlockHeader(), frame.getBlockchain()))
.build();
cleanFrame.pushStackItem(UInt256.of(blockNumber).getBytes());
operation.execute(cleanFrame);
return cleanFrame.popStackItem();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.ethereum.vm.operations;

import static java.util.Collections.emptyList;

import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockBody;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture;
import tech.pegasys.pantheon.ethereum.core.ExecutionContextTestFixture;
import tech.pegasys.pantheon.ethereum.core.MessageFrameTestFixture;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.services.kvstore.RocksDbKeyValueStorage;
import tech.pegasys.pantheon.util.uint.UInt256;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import com.google.common.io.MoreFiles;
import com.google.common.io.RecursiveDeleteOption;

public class OperationBenchmarkHelper {

private final Path storageDirectory;
private final RocksDbKeyValueStorage keyValueStorage;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be RocksDbKeyValueStorage? Can we use the abstract parent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need access to the close method which is only on RocksDbKeyValueStorage. I think I might follow up with another PR to add close to the KeyValueStorage interface which would improve this kind of thing in a few places, but outside this scope of this change.

private final MessageFrame messageFrame;

private OperationBenchmarkHelper(
final Path storageDirectory,
final RocksDbKeyValueStorage keyValueStorage,
final MessageFrame messageFrame) {
this.storageDirectory = storageDirectory;
this.keyValueStorage = keyValueStorage;
this.messageFrame = messageFrame;
}

public static OperationBenchmarkHelper create() throws IOException {
final Path storageDirectory = Files.createTempDirectory("benchmark");
final RocksDbKeyValueStorage keyValueStorage = RocksDbKeyValueStorage.create(storageDirectory);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use Provider and pass it into create?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My aim here is to make pull as much of the complexity out of the benchmark classes where it would have to be repeated into this helper class where it can be shared. This also isn't the type of benchmark where you'd test out multiple back-ends because the usage of storage is typically very light (even SSTORE wouldn't affect the key value storage in this scope).


final ExecutionContextTestFixture executionContext =
ExecutionContextTestFixture.builder().keyValueStorage(keyValueStorage).build();
final MutableBlockchain blockchain = executionContext.getBlockchain();

for (int i = 1; i < 256; i++) {
blockchain.appendBlock(
new Block(
new BlockHeaderTestFixture()
.parentHash(blockchain.getChainHeadHash())
.number(i)
.difficulty(UInt256.ONE)
.buildHeader(),
new BlockBody(emptyList(), emptyList())),
emptyList());
}
final MessageFrame messageFrame =
new MessageFrameTestFixture()
.executionContextTestFixture(executionContext)
.blockHeader(
new BlockHeaderTestFixture()
.parentHash(blockchain.getChainHeadHash())
.number(blockchain.getChainHeadBlockNumber() + 1)
.difficulty(UInt256.ONE)
.buildHeader())
.build();
return new OperationBenchmarkHelper(storageDirectory, keyValueStorage, messageFrame);
}

public MessageFrame createMessageFrame() {
return createMessageFrameBuilder().build();
}

public MessageFrame.Builder createMessageFrameBuilder() {
return MessageFrame.builder()
.type(MessageFrame.Type.MESSAGE_CALL)
.messageFrameStack(messageFrame.getMessageFrameStack())
.blockchain(messageFrame.getBlockchain())
.worldState(messageFrame.getWorldState())
.initialGas(messageFrame.getRemainingGas())
.address(messageFrame.getContractAddress())
.originator(messageFrame.getOriginatorAddress())
.contract(messageFrame.getRecipientAddress())
.gasPrice(messageFrame.getGasPrice())
.inputData(messageFrame.getInputData())
.sender(messageFrame.getSenderAddress())
.value(messageFrame.getValue())
.apparentValue(messageFrame.getApparentValue())
.code(messageFrame.getCode())
.blockHeader(messageFrame.getBlockHeader())
.depth(messageFrame.getMessageStackDepth())
.isStatic(messageFrame.isStatic())
.completer(messageFrame -> {})
.miningBeneficiary(messageFrame.getMiningBeneficiary())
.blockHashLookup(messageFrame.getBlockHashLookup());
}

public void cleanUp() throws IOException {
keyValueStorage.close();
MoreFiles.deleteRecursively(storageDirectory, RecursiveDeleteOption.ALLOW_INSECURE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,37 @@

public class ExecutionContextTestFixture {

private final Block genesis = GenesisConfig.mainnet().getBlock();
private final KeyValueStorage keyValueStorage = new InMemoryKeyValueStorage();
private final MutableBlockchain blockchain =
new DefaultMutableBlockchain(genesis, keyValueStorage, MainnetBlockHashFunction::createHash);
private final WorldStateArchive stateArchive =
new WorldStateArchive(new KeyValueStorageWorldStateStorage(keyValueStorage));

ProtocolSchedule<Void> protocolSchedule;
ProtocolContext<Void> protocolContext = new ProtocolContext<>(blockchain, stateArchive, null);

public ExecutionContextTestFixture() {
this(MainnetProtocolSchedule.create(2, 3, 10, 11, 12, -1, 42));
}
private final Block genesis;
private final KeyValueStorage keyValueStorage;
private final MutableBlockchain blockchain;
private final WorldStateArchive stateArchive;

private final ProtocolSchedule<Void> protocolSchedule;
private final ProtocolContext<Void> protocolContext;

public ExecutionContextTestFixture(final ProtocolSchedule<Void> protocolSchedule) {
GenesisConfig.mainnet()
.writeStateTo(
new DefaultMutableWorldState(new KeyValueStorageWorldStateStorage(keyValueStorage)));
private ExecutionContextTestFixture(
final ProtocolSchedule<Void> protocolSchedule, final KeyValueStorage keyValueStorage) {
final GenesisConfig<Void> genesisConfig = GenesisConfig.mainnet();
this.genesis = genesisConfig.getBlock();
this.keyValueStorage = keyValueStorage;
this.blockchain =
new DefaultMutableBlockchain(
genesis, keyValueStorage, MainnetBlockHashFunction::createHash);
this.stateArchive =
new WorldStateArchive(new KeyValueStorageWorldStateStorage(keyValueStorage));
this.protocolSchedule = protocolSchedule;
this.protocolContext = new ProtocolContext<>(blockchain, stateArchive, null);

genesisConfig.writeStateTo(
new DefaultMutableWorldState(new KeyValueStorageWorldStateStorage(keyValueStorage)));
}

public static ExecutionContextTestFixture create() {
return new Builder().build();
}

public static Builder builder() {
return new Builder();
}

public Block getGenesis() {
Expand All @@ -71,4 +83,30 @@ public ProtocolSchedule<Void> getProtocolSchedule() {
public ProtocolContext<Void> getProtocolContext() {
return protocolContext;
}

public static class Builder {

private KeyValueStorage keyValueStorage;
private ProtocolSchedule<Void> protocolSchedule;

public Builder keyValueStorage(final KeyValueStorage keyValueStorage) {
this.keyValueStorage = keyValueStorage;
return this;
}

public Builder protocolSchedule(final ProtocolSchedule<Void> protocolSchedule) {
this.protocolSchedule = protocolSchedule;
return this;
}

public ExecutionContextTestFixture build() {
if (protocolSchedule == null) {
protocolSchedule = MainnetProtocolSchedule.create(0, 0, 0, 0, 0, 0, 42);
}
if (keyValueStorage == null) {
keyValueStorage = new InMemoryKeyValueStorage();
}
return new ExecutionContextTestFixture(protocolSchedule, keyValueStorage);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public MessageFrameTestFixture messageFrameStack(final Deque<MessageFrame> messa
return this;
}

public MessageFrameTestFixture executionContextTestFixture(
final ExecutionContextTestFixture executionContextTestFixture) {
this.executionContextTestFixture = executionContextTestFixture;
return this;
}

public MessageFrameTestFixture blockchain(final Blockchain blockchain) {
this.blockchain = Optional.of(blockchain);
return this;
Expand Down Expand Up @@ -181,7 +187,7 @@ private Blockchain createDefaultBlockchain() {
private ExecutionContextTestFixture getOrCreateExecutionContextTestFixture() {
// Avoid creating a test fixture if the test supplies the blockchain and worldstate.
if (executionContextTestFixture == null) {
executionContextTestFixture = new ExecutionContextTestFixture();
executionContextTestFixture = ExecutionContextTestFixture.create();
}
return executionContextTestFixture;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class TestCodeExecutor {
private static final Address SENDER_ADDRESS = AddressHelpers.ofValue(244259721);

public TestCodeExecutor(final ProtocolSchedule<Void> protocolSchedule) {
fixture = new ExecutionContextTestFixture(protocolSchedule);
fixture = ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build();
}

public MessageFrame executeCode(
Expand Down