Merge bitcoin/bitcoin#31179: RPC: Add reserve member function to UniValue and use it in blockToJSON function

5d82d92aff rpc: reserve space for `UniValue` variables  in `blockToJSON` (ismaelsadeeq)
6a506d5c37 UniValue: add reserve member function (ismaelsadeeq)
bd461195f4 bench: support benching all verbosity of `BlockToJson` (ismaelsadeeq)

Pull request description:

  This PR is motivated by https://github.com/bitcoin/bitcoin/issues/30495#issuecomment-2444881418,
  It adds a `reserve` member function to `UniValue` and applies it within the `blockToJSON` function to pre-allocate memory, minimizing reallocation's.

  <details>
  <summary>Slight performance increase in verbosity 1</summary>

  On **master**:

  |         ns/op |        op/s |  err% | total | benchmark              |
  |--------------:|------------:|------:|------:|:------------------------|
  |      190,342  |     5,254   |  2.3% |  0.01 | `BlockToJsonVerbose1`   |
  |  34,812,292   |       28.73 |  1.0% |  0.38 | `BlockToJsonVerbose2`   |
  |  34,457,167   |       29.02 |  1.0% |  0.38 | `BlockToJsonVerbose3`   |

  On **this PR**:

  |         ns/op |        op/s |  err% | total | benchmark              |
  |--------------:|------------:|------:|------:|:------------------------|
  |      172,278  |     5,805   |  0.7% |  0.01 | `BlockToJsonVerbose1`   |
  |  33,720,584   |       29.66 |  0.4% |  0.37 | `BlockToJsonVerbose2`   |
  |  33,884,417   |       29.51 |  1.2% |  0.38 | `BlockToJsonVerbose3`   |
  </details>

ACKs for top commit:
  maflcko:
    review ACK 5d82d92aff 🐀
  l0rinc:
    ACK 5d82d92aff
  achow101:
    ACK 5d82d92aff
  Eunovo:
    Re-ACK 5d82d92aff

Tree-SHA512: bdd2c1bcdc4d060d30ad3be0b10f0d722dda0c2286bc4156af851503220e8854e76a4dc53456826b543c110982455268838172d3a1026eee754d4c673b48ea05
This commit is contained in:
Ava Chow
2025-07-23 13:29:07 -07:00
5 changed files with 33 additions and 3 deletions

View File

@@ -45,17 +45,34 @@ struct TestBlockAndIndex {
} // namespace
static void BlockToJsonVerbose(benchmark::Bench& bench)
static void BlockToJson(benchmark::Bench& bench, TxVerbosity verbosity)
{
TestBlockAndIndex data;
const uint256 pow_limit{data.testing_setup->m_node.chainman->GetParams().GetConsensus().powLimit};
bench.run([&] {
auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, data.blockindex, data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT, pow_limit);
auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, data.blockindex, data.blockindex, verbosity, pow_limit);
ankerl::nanobench::doNotOptimizeAway(univalue);
});
}
BENCHMARK(BlockToJsonVerbose, benchmark::PriorityLevel::HIGH);
static void BlockToJsonVerbosity1(benchmark::Bench& bench)
{
BlockToJson(bench, TxVerbosity::SHOW_TXID);
}
static void BlockToJsonVerbosity2(benchmark::Bench& bench)
{
BlockToJson(bench, TxVerbosity::SHOW_DETAILS);
}
static void BlockToJsonVerbosity3(benchmark::Bench& bench)
{
BlockToJson(bench, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
}
BENCHMARK(BlockToJsonVerbosity1, benchmark::PriorityLevel::HIGH);
BENCHMARK(BlockToJsonVerbosity2, benchmark::PriorityLevel::HIGH);
BENCHMARK(BlockToJsonVerbosity3, benchmark::PriorityLevel::HIGH);
static void BlockToJsonVerboseWrite(benchmark::Bench& bench)
{

View File

@@ -181,6 +181,7 @@ void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry
entry.pushKV("locktime", (int64_t)tx.nLockTime);
UniValue vin{UniValue::VARR};
vin.reserve(tx.vin.size());
// If available, use Undo data to calculate the fee. Note that txundo == nullptr
// for coinbase transactions and for transactions where undo data is unavailable.
@@ -203,6 +204,7 @@ void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry
}
if (!tx.vin[i].scriptWitness.IsNull()) {
UniValue txinwitness(UniValue::VARR);
txinwitness.reserve(tx.vin[i].scriptWitness.stack.size());
for (const auto& item : tx.vin[i].scriptWitness.stack) {
txinwitness.push_back(HexStr(item));
}
@@ -232,6 +234,7 @@ void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry
entry.pushKV("vin", std::move(vin));
UniValue vout(UniValue::VARR);
vout.reserve(tx.vout.size());
for (unsigned int i = 0; i < tx.vout.size(); i++) {
const CTxOut& txout = tx.vout[i];

View File

@@ -186,6 +186,7 @@ UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIn
result.pushKV("size", (int)::GetSerializeSize(TX_WITH_WITNESS(block)));
result.pushKV("weight", (int)::GetBlockWeight(block));
UniValue txs(UniValue::VARR);
txs.reserve(block.vtx.size());
switch (verbosity) {
case TxVerbosity::SHOW_TXID:

View File

@@ -70,6 +70,8 @@ public:
size_t size() const { return values.size(); }
void reserve(size_t new_cap);
void getObjMap(std::map<std::string,UniValue>& kv) const;
bool checkObject(const std::map<std::string,UniValue::VType>& memberTypes) const;
const UniValue& operator[](const std::string& key) const;

View File

@@ -240,3 +240,10 @@ const UniValue& UniValue::find_value(std::string_view key) const
return NullUniValue;
}
void UniValue::reserve(size_t new_cap)
{
values.reserve(new_cap);
if (typ == VOBJ) {
keys.reserve(new_cap);
}
}