From 18dacf9bd25154e184b097ee4e8f786d9be25637 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Sun, 2 Oct 2016 17:38:48 -0400 Subject: [PATCH] Add microbenchmarks to profile more code paths. The new benchmarks exercise script validation, CCoinsDBView caching, mempool eviction, and wallet coin selection code. All of the benchmarks added here are extremely simple and don't necessarily mirror common real world conditions or interesting performance edge cases. Details about how specific benchmarks can be improved are noted in comments. Github-Issue: #7883 --- src/Makefile.bench.include | 6 +- src/bench/ccoins_caching.cpp | 87 +++++++++++++++++++++++++ src/bench/coin_selection.cpp | 62 ++++++++++++++++++ src/bench/mempool_eviction.cpp | 115 +++++++++++++++++++++++++++++++++ src/bench/verify_script.cpp | 103 +++++++++++++++++++++++++++++ 5 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 src/bench/ccoins_caching.cpp create mode 100644 src/bench/coin_selection.cpp create mode 100644 src/bench/mempool_eviction.cpp create mode 100644 src/bench/verify_script.cpp diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 8c024a8c4..c83432e91 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -14,6 +14,9 @@ bench_bench_bitcoin_SOURCES = \ bench/Examples.cpp \ bench/rollingbloom.cpp \ bench/crypto_hash.cpp \ + bench/ccoins_caching.cpp \ + bench/mempool_eviction.cpp \ + bench/verify_script.cpp \ bench/base58.cpp bench_bench_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CLFAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/ @@ -34,7 +37,8 @@ bench_bench_bitcoin_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif if ENABLE_WALLET -bench_bench_bitcoin_LDADD += $(LIBBITCOIN_WALLET) +bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp +bench_bench_bitcoin_LDADD += $(LIBBITCOIN_WALLET) $(LIBBITCOIN_CRYPTO) endif bench_bench_bitcoin_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp new file mode 100644 index 000000000..1e8e3d462 --- /dev/null +++ b/src/bench/ccoins_caching.cpp @@ -0,0 +1,87 @@ +// Copyright (c) 2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" +#include "coins.h" +#include "policy/policy.h" +#include "wallet/crypter.h" + +#include + +// FIXME: Dedup with SetupDummyInputs in test/transaction_tests.cpp. +// +// Helper: create two dummy transactions, each with +// two outputs. The first has 11 and 50 CENT outputs +// paid to a TX_PUBKEY, the second 21 and 22 CENT outputs +// paid to a TX_PUBKEYHASH. +// +static std::vector +SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) +{ + std::vector dummyTransactions; + dummyTransactions.resize(2); + + // Add some keys to the keystore: + CKey key[4]; + for (int i = 0; i < 4; i++) { + key[i].MakeNewKey(i % 2); + keystoreRet.AddKey(key[i]); + } + + // Create some dummy input transactions + dummyTransactions[0].vout.resize(2); + dummyTransactions[0].vout[0].nValue = 11 * CENT; + dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; + dummyTransactions[0].vout[1].nValue = 50 * CENT; + dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; + coinsRet.ModifyCoins(dummyTransactions[0].GetHash())->FromTx(dummyTransactions[0], 0); + + dummyTransactions[1].vout.resize(2); + dummyTransactions[1].vout[0].nValue = 21 * CENT; + dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); + dummyTransactions[1].vout[1].nValue = 22 * CENT; + dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); + coinsRet.ModifyCoins(dummyTransactions[1].GetHash())->FromTx(dummyTransactions[1], 0); + + return dummyTransactions; +} + +// Microbenchmark for simple accesses to a CCoinsViewCache database. Note from +// laanwj, "replicating the actual usage patterns of the client is hard though, +// many times micro-benchmarks of the database showed completely different +// characteristics than e.g. reindex timings. But that's not a requirement of +// every benchmark." +// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) +static void CCoinsCaching(benchmark::State& state) +{ + CBasicKeyStore keystore; + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + std::vector dummyTransactions = SetupDummyInputs(keystore, coins); + + CMutableTransaction t1; + t1.vin.resize(3); + t1.vin[0].prevout.hash = dummyTransactions[0].GetHash(); + t1.vin[0].prevout.n = 1; + t1.vin[0].scriptSig << std::vector(65, 0); + t1.vin[1].prevout.hash = dummyTransactions[1].GetHash(); + t1.vin[1].prevout.n = 0; + t1.vin[1].scriptSig << std::vector(65, 0) << std::vector(33, 4); + t1.vin[2].prevout.hash = dummyTransactions[1].GetHash(); + t1.vin[2].prevout.n = 1; + t1.vin[2].scriptSig << std::vector(65, 0) << std::vector(33, 4); + t1.vout.resize(2); + t1.vout[0].nValue = 90 * CENT; + t1.vout[0].scriptPubKey << OP_1; + + // Benchmark. + while (state.KeepRunning()) { + bool success = AreInputsStandard(t1, coins); + assert(success); + CAmount value = coins.GetValueIn(t1); + assert(value == (50 + 21 + 22) * CENT); + } +} + +BENCHMARK(CCoinsCaching); diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp new file mode 100644 index 000000000..7091ee3e1 --- /dev/null +++ b/src/bench/coin_selection.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2012-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" +#include "wallet/wallet.h" + +#include +#include + +using namespace std; + +static void addCoin(const CAmount& nValue, const CWallet& wallet, vector& vCoins) +{ + int nInput = 0; + + static int nextLockTime = 0; + CMutableTransaction tx; + tx.nLockTime = nextLockTime++; // so all transactions get different hashes + tx.vout.resize(nInput + 1); + tx.vout[nInput].nValue = nValue; + CWalletTx* wtx = new CWalletTx(&wallet, tx); + + int nAge = 6 * 24; + COutput output(wtx, nInput, nAge, true, true); + vCoins.push_back(output); +} + +// Simple benchmark for wallet coin selection. Note that it maybe be necessary +// to build up more complicated scenarios in order to get meaningful +// measurements of performance. From laanwj, "Wallet coin selection is probably +// the hardest, as you need a wider selection of scenarios, just testing the +// same one over and over isn't too useful. Generating random isn't useful +// either for measurements." +// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) +static void CoinSelection(benchmark::State& state) +{ + const CWallet wallet; + vector vCoins; + LOCK(wallet.cs_wallet); + + while (state.KeepRunning()) { + // Empty wallet. + BOOST_FOREACH (COutput output, vCoins) + delete output.tx; + vCoins.clear(); + + // Add coins. + for (int i = 0; i < 1000; i++) + addCoin(1000 * COIN, wallet, vCoins); + addCoin(3 * COIN, wallet, vCoins); + + set > setCoinsRet; + CAmount nValueRet; + bool success = wallet.SelectCoinsMinConf(1003 * COIN, 1, 6, vCoins, setCoinsRet, nValueRet); + assert(success); + assert(nValueRet == 1003 * COIN); + assert(setCoinsRet.size() == 2); + } +} + +BENCHMARK(CoinSelection); diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp new file mode 100644 index 000000000..0ae69c75f --- /dev/null +++ b/src/bench/mempool_eviction.cpp @@ -0,0 +1,115 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" +#include "policy/policy.h" +#include "txmempool.h" + +#include +#include + +static void AddTx(const CTransaction& tx, const CAmount& nFee, CTxMemPool& pool) +{ + int64_t nTime = 0; + double dPriority = 10.0; + unsigned int nHeight = 1; + bool spendsCoinbase = false; + unsigned int sigOpCost = 4; + LockPoints lp; + pool.addUnchecked(tx.GetHash(), CTxMemPoolEntry( + tx, nFee, nTime, dPriority, nHeight, pool.HasNoInputsOf(tx), + tx.GetValueOut(), spendsCoinbase, sigOpCost, lp)); +} + +// Right now this is only testing eviction performance in an extremely small +// mempool. Code needs to be written to generate a much wider variety of +// unique transactions for a more meaningful performance measurement. +static void MempoolEviction(benchmark::State& state) +{ + CMutableTransaction tx1 = CMutableTransaction(); + tx1.vin.resize(1); + tx1.vin[0].scriptSig = CScript() << OP_1; + tx1.vout.resize(1); + tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL; + tx1.vout[0].nValue = 10 * COIN; + + CMutableTransaction tx2 = CMutableTransaction(); + tx2.vin.resize(1); + tx2.vin[0].scriptSig = CScript() << OP_2; + tx2.vout.resize(1); + tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL; + tx2.vout[0].nValue = 10 * COIN; + + CMutableTransaction tx3 = CMutableTransaction(); + tx3.vin.resize(1); + tx3.vin[0].prevout = COutPoint(tx2.GetHash(), 0); + tx3.vin[0].scriptSig = CScript() << OP_2; + tx3.vout.resize(1); + tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL; + tx3.vout[0].nValue = 10 * COIN; + + CMutableTransaction tx4 = CMutableTransaction(); + tx4.vin.resize(2); + tx4.vin[0].prevout.SetNull(); + tx4.vin[0].scriptSig = CScript() << OP_4; + tx4.vin[1].prevout.SetNull(); + tx4.vin[1].scriptSig = CScript() << OP_4; + tx4.vout.resize(2); + tx4.vout[0].scriptPubKey = CScript() << OP_4 << OP_EQUAL; + tx4.vout[0].nValue = 10 * COIN; + tx4.vout[1].scriptPubKey = CScript() << OP_4 << OP_EQUAL; + tx4.vout[1].nValue = 10 * COIN; + + CMutableTransaction tx5 = CMutableTransaction(); + tx5.vin.resize(2); + tx5.vin[0].prevout = COutPoint(tx4.GetHash(), 0); + tx5.vin[0].scriptSig = CScript() << OP_4; + tx5.vin[1].prevout.SetNull(); + tx5.vin[1].scriptSig = CScript() << OP_5; + tx5.vout.resize(2); + tx5.vout[0].scriptPubKey = CScript() << OP_5 << OP_EQUAL; + tx5.vout[0].nValue = 10 * COIN; + tx5.vout[1].scriptPubKey = CScript() << OP_5 << OP_EQUAL; + tx5.vout[1].nValue = 10 * COIN; + + CMutableTransaction tx6 = CMutableTransaction(); + tx6.vin.resize(2); + tx6.vin[0].prevout = COutPoint(tx4.GetHash(), 1); + tx6.vin[0].scriptSig = CScript() << OP_4; + tx6.vin[1].prevout.SetNull(); + tx6.vin[1].scriptSig = CScript() << OP_6; + tx6.vout.resize(2); + tx6.vout[0].scriptPubKey = CScript() << OP_6 << OP_EQUAL; + tx6.vout[0].nValue = 10 * COIN; + tx6.vout[1].scriptPubKey = CScript() << OP_6 << OP_EQUAL; + tx6.vout[1].nValue = 10 * COIN; + + CMutableTransaction tx7 = CMutableTransaction(); + tx7.vin.resize(2); + tx7.vin[0].prevout = COutPoint(tx5.GetHash(), 0); + tx7.vin[0].scriptSig = CScript() << OP_5; + tx7.vin[1].prevout = COutPoint(tx6.GetHash(), 0); + tx7.vin[1].scriptSig = CScript() << OP_6; + tx7.vout.resize(2); + tx7.vout[0].scriptPubKey = CScript() << OP_7 << OP_EQUAL; + tx7.vout[0].nValue = 10 * COIN; + tx7.vout[1].scriptPubKey = CScript() << OP_7 << OP_EQUAL; + tx7.vout[1].nValue = 10 * COIN; + + CTxMemPool pool(CFeeRate(1000)); + + while (state.KeepRunning()) { + AddTx(tx1, 10000LL, pool); + AddTx(tx2, 5000LL, pool); + AddTx(tx3, 20000LL, pool); + AddTx(tx4, 7000LL, pool); + AddTx(tx5, 1000LL, pool); + AddTx(tx6, 1100LL, pool); + AddTx(tx7, 9000LL, pool); + pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); + pool.TrimToSize(GetVirtualTransactionSize(tx1)); + } +} + +BENCHMARK(MempoolEviction); diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp new file mode 100644 index 000000000..dc3940cdb --- /dev/null +++ b/src/bench/verify_script.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench.h" +#include "key.h" +#if defined(HAVE_CONSENSUS_LIB) +#include "script/bitcoinconsensus.h" +#endif +#include "script/script.h" +#include "script/sign.h" +#include "streams.h" + +// FIXME: Dedup with BuildCreditingTransaction in test/script_tests.cpp. +static CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey) +{ + CMutableTransaction txCredit; + txCredit.nVersion = 1; + txCredit.nLockTime = 0; + txCredit.vin.resize(1); + txCredit.vout.resize(1); + txCredit.vin[0].prevout.SetNull(); + txCredit.vin[0].scriptSig = CScript() << CScriptNum(0) << CScriptNum(0); + txCredit.vin[0].nSequence = CTxIn::SEQUENCE_FINAL; + txCredit.vout[0].scriptPubKey = scriptPubKey; + txCredit.vout[0].nValue = 1; + + return txCredit; +} + +// FIXME: Dedup with BuildSpendingTransaction in test/script_tests.cpp. +static CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CMutableTransaction& txCredit) +{ + CMutableTransaction txSpend; + txSpend.nVersion = 1; + txSpend.nLockTime = 0; + txSpend.vin.resize(1); + txSpend.vout.resize(1); + txSpend.wit.vtxinwit.resize(1); + txSpend.vin[0].prevout.hash = txCredit.GetHash(); + txSpend.vin[0].prevout.n = 0; + txSpend.vin[0].scriptSig = scriptSig; + txSpend.vin[0].nSequence = CTxIn::SEQUENCE_FINAL; + txSpend.vout[0].scriptPubKey = CScript(); + txSpend.vout[0].nValue = txCredit.vout[0].nValue; + + return txSpend; +} + +// Microbenchmark for verification of a basic P2WPKH script. Can be easily +// modified to measure performance of other types of scripts. +static void VerifyScriptBench(benchmark::State& state) +{ + const int flags = SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH; + const int witnessversion = 0; + + // Keypair. + CKey key; + const unsigned char vchKey[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + key.Set(vchKey, vchKey + 32, false); + CPubKey pubkey = key.GetPubKey(); + uint160 pubkeyHash; + CHash160().Write(pubkey.begin(), pubkey.size()).Finalize(pubkeyHash.begin()); + + // Script. + CScript scriptPubKey = CScript() << witnessversion << ToByteVector(pubkeyHash); + CScript scriptSig; + CScript witScriptPubkey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(pubkeyHash) << OP_EQUALVERIFY << OP_CHECKSIG; + CTransaction txCredit = BuildCreditingTransaction(scriptPubKey); + CMutableTransaction txSpend = BuildSpendingTransaction(scriptSig, txCredit); + CScriptWitness& witness = txSpend.wit.vtxinwit[0].scriptWitness; + witness.stack.emplace_back(); + key.Sign(SignatureHash(witScriptPubkey, txSpend, 0, SIGHASH_ALL, txCredit.vout[0].nValue, SIGVERSION_WITNESS_V0), witness.stack.back(), 0); + witness.stack.back().push_back(static_cast(SIGHASH_ALL)); + witness.stack.push_back(ToByteVector(pubkey)); + + // Benchmark. + while (state.KeepRunning()) { + ScriptError err; + bool success = VerifyScript( + txSpend.vin[0].scriptSig, + txCredit.vout[0].scriptPubKey, + &txSpend.wit.vtxinwit[0].scriptWitness, + flags, + MutableTransactionSignatureChecker(&txSpend, 0, txCredit.vout[0].nValue), + &err); + assert(err == SCRIPT_ERR_OK); + assert(success); + +#if defined(HAVE_CONSENSUS_LIB) + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << txSpend; + int csuccess = bitcoinconsensus_verify_script_with_amount( + begin_ptr(txCredit.vout[0].scriptPubKey), + txCredit.vout[0].scriptPubKey.size(), + txCredit.vout[0].nValue, + (const unsigned char*)&stream[0], stream.size(), 0, flags, nullptr); + assert(csuccess == 1); +#endif + } +} + +BENCHMARK(VerifyScriptBench);