coins: reduce lookups in dbcache layer propagation

Previously, when the parent coins cache had no entry and the child did, `BatchWrite` performed a find followed by `try_emplace`, which resulted in multiple `SipHash` computations and bucket traversals on the common insert path.

This change uses a single leading `try_emplace` and branches on the returned `inserted` flag.
In the `FRESH && SPENT` case (only exercised by tests), we erase the just-inserted placeholder (which is constant time with no rehash anyway).
Semantics are unchanged for all valid parent/child state combinations.

This change is a minimal version of https://github.com/bitcoin/bitcoin/pull/32128/commits/723c49b63bb10da843fbb6efc6928dca415cc47f and draws simplification ideas https://github.com/bitcoin/bitcoin/pull/30673/commits/ae76ec7bcff0a08a61f294882a71e46d177b009f.

Added TODO versions for related pre-existing issues that should be fixed in follow-ups.

Co-authored-by: Martin Ankerl <martin.ankerl@gmail.com>
Co-authored-by: Andrew Toth <andrewstoth@gmail.com>
Co-authored-by: optout <13562139+optout21@users.noreply.github.com>
This commit is contained in:
committed 2025-11-01 22:29:42 +01:00
1 parent 25c45bb0d0
commit c8f5e446dc
1 file changed
+8 -10
+8 -10
View File
@@ -185,18 +185,16 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlockIn) { bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlockIn) {
for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) { for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
// Ignore non-dirty entries (optimization). if (!it->second.IsDirty()) { // TODO a cursor can only contain dirty entries
if (!it->second.IsDirty()) {
continue; continue;
} }
CCoinsMap::iterator itUs = cacheCoins.find(it->first); auto [itUs, inserted]{cacheCoins.try_emplace(it->first)};
if (itUs == cacheCoins.end()) { if (inserted) {
// The parent cache does not have an entry, while the child cache does. if (it->second.IsFresh() && it->second.coin.IsSpent()) {
// We can ignore it if it's both spent and FRESH in the child cacheCoins.erase(itUs); // TODO fresh coins should have been removed at spend
if (!(it->second.IsFresh() && it->second.coin.IsSpent())) { } else {
// Create the coin in the parent cache, move the data up // The parent cache does not have an entry, while the child cache does.
// and mark it as dirty. // Move the data up and mark it as dirty.
itUs = cacheCoins.try_emplace(it->first).first;
CCoinsCacheEntry& entry{itUs->second}; CCoinsCacheEntry& entry{itUs->second};
assert(entry.coin.DynamicMemoryUsage() == 0); assert(entry.coin.DynamicMemoryUsage() == 0);
if (cursor.WillErase(*it)) { if (cursor.WillErase(*it)) {