Skip to content

Commit 3dd815f

Browse files
l0rincoptout21hodlinator
committed
validation: pre-reserve leaves to prevent reallocs with odd vtx count
`ComputeMerkleRoot` duplicates the last hash when the input size is odd. If the caller provides a `std::vector` whose capacity equals its size, that extra `push_back` forces a reallocation, doubling its capacity (allocating 3x the necessary memory). This affects roughly half of the created blocks (those with odd transaction counts), causing unnecessary memory fragmentation during every block validation. Fix this by pre-reserving the vector capacity to account for the odd-count duplication. The expression `(size + 1) & ~1ULL` adds 1 to the size and clears the last bit, effectively rounding up to the next even number. This syntax produces optimal assembly across x86/ARM and 32/64-bit platforms for gcc/clang, see https://godbolt.org/z/xzscoq7nv. Also switch from `resize` to `reserve` + `push_back` to eliminate the default construction of `uint256` objects that are immediately overwritten. > ./build/bin/bench_bitcoin -filter='MerkleRoot.*' -min-time=1000 | ns/leaf | leaf/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 43.73 | 22,867,350.51 | 0.0% | 1.10 | `MerkleRoot` | 44.17 | 22,640,349.14 | 0.0% | 1.10 | `MerkleRootWithMutation` Massif memory measurements after show 0.8 MB peak memory usage KB 801.4^ # | # | # | # | # | # | # | # :::::@:::::@: | #:::@@@::@:::::::::::::::@::@:@:::@@:::::::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: | #:::@ @: @:::::::::::::::@::@:@:::@ :::: ::::@::::::@:::::@::::@:::::@: 0 +----------------------------------------------------------------------->s 0 227.5 and the stacks don't show reallocs anymore: 96.37% (790,809B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->35.10% (288,064B) 0x2234AF: allocate (new_allocator.h:151) | ->35.10% (288,064B) 0x2234AF: allocate (allocator.h:203) | ->35.10% (288,064B) 0x2234AF: allocate (alloc_traits.h:614) | ->35.10% (288,064B) 0x2234AF: _M_allocate (stl_vector.h:387) | ->35.10% (288,064B) 0x2234AF: reserve (vector.tcc:79) | ->35.10% (288,064B) 0x2234AF: ToMerkleLeaves<std::vector<uint256>, MerkleRoot(ankerl::nanobench::Bench&)::<lambda()>::<lambda(bool, const auto:46&)> > (merkle.h:19) | ->35.10% (288,064B) 0x2234AF: operator() (merkle_root.cpp:25) | ->35.10% (288,064B) 0x2234AF: ankerl::nanobench::Bench& ankerl::nanobench::Bench::run<MerkleRoot(ankerl::nanobench::Bench&)::{lambda() Co-authored-by: optout21 <[email protected]> Co-authored-by: Hodlinator <[email protected]>
1 parent 7fd47e0 commit 3dd815f

File tree

3 files changed

+10
-10
lines changed

3 files changed

+10
-10
lines changed

src/bench/merkle_root.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ static void MerkleRoot(benchmark::Bench& bench)
2424
for (bool mutate : {false, true}) {
2525
bench.name(mutate ? "MerkleRootWithMutation" : "MerkleRoot").batch(hashes.size()).unit("leaf").run([&] {
2626
std::vector<uint256> leaves;
27-
leaves.resize(hashes.size());
27+
leaves.reserve((hashes.size() + 1) & ~1ULL); // capacity rounded up to even
2828
for (size_t s = 0; s < hashes.size(); s++) {
29-
leaves[s] = hashes[s];
29+
leaves.push_back(hashes[s]);
3030
}
3131

3232
bool mutated{false};

src/consensus/merkle.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,20 @@ uint256 ComputeMerkleRoot(std::vector<uint256> hashes, bool* mutated) {
6666
uint256 BlockMerkleRoot(const CBlock& block, bool* mutated)
6767
{
6868
std::vector<uint256> leaves;
69-
leaves.resize(block.vtx.size());
69+
leaves.reserve((block.vtx.size() + 1) & ~1ULL); // capacity rounded up to even
7070
for (size_t s = 0; s < block.vtx.size(); s++) {
71-
leaves[s] = block.vtx[s]->GetHash().ToUint256();
71+
leaves.push_back(block.vtx[s]->GetHash().ToUint256());
7272
}
7373
return ComputeMerkleRoot(std::move(leaves), mutated);
7474
}
7575

7676
uint256 BlockWitnessMerkleRoot(const CBlock& block)
7777
{
7878
std::vector<uint256> leaves;
79-
leaves.resize(block.vtx.size());
80-
leaves[0].SetNull(); // The witness hash of the coinbase is 0.
79+
leaves.reserve((block.vtx.size() + 1) & ~1ULL); // capacity rounded up to even
80+
leaves.emplace_back(); // The witness hash of the coinbase is 0.
8181
for (size_t s = 1; s < block.vtx.size(); s++) {
82-
leaves[s] = block.vtx[s]->GetWitnessHash().ToUint256();
82+
leaves.push_back(block.vtx[s]->GetWitnessHash().ToUint256());
8383
}
8484
return ComputeMerkleRoot(std::move(leaves));
8585
}

src/signet.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ static bool FetchAndClearCommitmentSection(const std::span<const uint8_t> header
5858
static uint256 ComputeModifiedMerkleRoot(const CMutableTransaction& cb, const CBlock& block)
5959
{
6060
std::vector<uint256> leaves;
61-
leaves.resize(block.vtx.size());
62-
leaves[0] = cb.GetHash().ToUint256();
61+
leaves.reserve((block.vtx.size() + 1) & ~1ULL); // capacity rounded up to even
62+
leaves.push_back(cb.GetHash().ToUint256());
6363
for (size_t s = 1; s < block.vtx.size(); ++s) {
64-
leaves[s] = block.vtx[s]->GetHash().ToUint256();
64+
leaves.push_back(block.vtx[s]->GetHash().ToUint256());
6565
}
6666
return ComputeMerkleRoot(std::move(leaves));
6767
}

0 commit comments

Comments
 (0)