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 723c49b63b and draws simplification ideas ae76ec7bcf.

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:
Lőrinc
2025-09-01 21:29:37 -07:00
parent 25c45bb0d0
commit c8f5e446dc

View File

@@ -185,18 +185,16 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlockIn) {
for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
// Ignore non-dirty entries (optimization).
if (!it->second.IsDirty()) {
if (!it->second.IsDirty()) { // TODO a cursor can only contain dirty entries
continue;
}
CCoinsMap::iterator itUs = cacheCoins.find(it->first);
if (itUs == cacheCoins.end()) {
// The parent cache does not have an entry, while the child cache does.
// We can ignore it if it's both spent and FRESH in the child
if (!(it->second.IsFresh() && it->second.coin.IsSpent())) {
// Create the coin in the parent cache, move the data up
// and mark it as dirty.
itUs = cacheCoins.try_emplace(it->first).first;
auto [itUs, inserted]{cacheCoins.try_emplace(it->first)};
if (inserted) {
if (it->second.IsFresh() && it->second.coin.IsSpent()) {
cacheCoins.erase(itUs); // TODO fresh coins should have been removed at spend
} else {
// The parent cache does not have an entry, while the child cache does.
// Move the data up and mark it as dirty.
CCoinsCacheEntry& entry{itUs->second};
assert(entry.coin.DynamicMemoryUsage() == 0);
if (cursor.WillErase(*it)) {