Summary
ComputeMerkleRoot
duplicates the last hash when the input size is odd.
If the caller provides a std::vector
whose capacity equals its size (common when the vector was created with resize()
), that extra push_back
forces a reallocation, doubling its capacity.
We pay this penalty on every Merkle root calculation even though we know the final size in advance.
Fix
- Call sites: replace
resize(n)
+ index assignment withreserve(n + (n & 1))
+emplace_back(...)
; - Guarantees one extra slot when
vtx.size()
is odd (assigned later); - Removes default construction of
uint256
objects we overwrite anyway.
Validation
The benchmark was updated to use an even leaf count for simplicity and to focus on hashing speed rather than reallocations or vector copying.
Asserts (not pushed in this PR) confirm that push_back
never reallocates and that the coinbase witness hash remains null:
0if (hashes.size() & 1) {
1 assert(hashes.size() < hashes.capacity()); // TODO remove
2 hashes.push_back(hashes.back());
3}
4
5leaves.reserve(block.vtx.size() + (block.vtx.size() & 1)); // capacity rounded up to even
6leaves.emplace_back();
7assert(leaves.back().IsNull()); // TODO remove
A full -reindex-chainstate
up to block 896 408 ran without triggering the asserts.