From 058b08c147a6d56b57221faa5b6fcdb83b4140b2 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 3 Sep 2014 09:37:47 +0200 Subject: [PATCH] Do not keep fully spent but unwritten CCoins entries cached. Instead of storing CCoins entries directly in CCoinsMap, store a CCoinsCacheEntry which additionally keeps track of whether a particular entry is: * dirty: potentially different from its parent view. * fresh: the parent view is known to not have a non-pruned version. This allows us to skip non-dirty cache entries when pushing batches of changes up, and to remove CCoins entries about transactions that are fully spent before the parent cache learns about them. --- src/coins.cpp | 85 +++++++++++++++++++++++++++++++++++++-------------- src/coins.h | 19 ++++++++++-- src/main.cpp | 4 +-- src/txdb.cpp | 11 +++++-- 4 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 9632e67f2..9d60089bf 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -71,18 +71,6 @@ CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hasModifier(false), hashBlock(0) { } -bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { - if (cacheCoins.count(txid)) { - coins = cacheCoins[txid]; - return true; - } - if (base->GetCoins(txid, coins)) { - cacheCoins[txid] = coins; - return true; - } - return false; -} - CCoinsViewCache::~CCoinsViewCache() { assert(!hasModifier); @@ -93,21 +81,43 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const if (it != cacheCoins.end()) return it; CCoins tmp; - if (!base->GetCoins(txid,tmp)) + if (!base->GetCoins(txid, tmp)) return cacheCoins.end(); - CCoinsMap::iterator ret = cacheCoins.insert(it, std::make_pair(txid, CCoins())); - tmp.swap(ret->second); + CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first; + tmp.swap(ret->second.coins); + if (ret->second.coins.IsPruned()) { + // The parent only has an empty entry for this txid; we can consider our + // version as fresh. + ret->second.flags = CCoinsCacheEntry::FRESH; + } return ret; } +bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { + CCoinsMap::const_iterator it = FetchCoins(txid); + if (it != cacheCoins.end()) { + coins = it->second.coins; + return true; + } + return false; +} + CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) { assert(!hasModifier); hasModifier = true; - std::pair ret = cacheCoins.insert(std::make_pair(txid, CCoins())); + std::pair ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); if (ret.second) { - if (!base->GetCoins(txid, ret.first->second)) - ret.first->second.Clear(); + if (!base->GetCoins(txid, ret.first->second.coins)) { + // The parent view does not have this entry; mark it as fresh. + ret.first->second.coins.Clear(); + ret.first->second.flags = CCoinsCacheEntry::FRESH; + } else if (ret.first->second.coins.IsPruned()) { + // The parent view only has a pruned entry for this; mark it as fresh. + ret.first->second.flags = CCoinsCacheEntry::FRESH; + } } + // Assume that whenever ModifyCoins is called, the entry will be modified. + ret.first->second.flags |= CCoinsCacheEntry::DIRTY; return CCoinsModifier(*this, ret.first); } @@ -116,7 +126,7 @@ const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const { if (it == cacheCoins.end()) { return NULL; } else { - return &it->second; + return &it->second.coins; } } @@ -126,7 +136,7 @@ bool CCoinsViewCache::HaveCoins(const uint256 &txid) const { // as we only care about the case where an transaction was replaced entirely // in a reorganization (which wipes vout entirely, as opposed to spending // which just cleans individual outputs). - return (it != cacheCoins.end() && !it->second.vout.empty()); + return (it != cacheCoins.end() && !it->second.coins.vout.empty()); } uint256 CCoinsViewCache::GetBestBlock() const { @@ -142,7 +152,32 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) { assert(!hasModifier); for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { - cacheCoins[it->first].swap(it->second); + if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization). + CCoinsMap::iterator itUs = cacheCoins.find(it->first); + if (itUs == cacheCoins.end()) { + if (!it->second.coins.IsPruned()) { + // The parent cache does not have an entry, while the child + // cache does have (a non-pruned) one. Move the data up, and + // mark it as fresh (if the grandparent did have it, we + // would have pulled it in at first GetCoins). + assert(it->second.flags & CCoinsCacheEntry::FRESH); + CCoinsCacheEntry& entry = cacheCoins[it->first]; + entry.coins.swap(it->second.coins); + entry.flags = CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH; + } + } else { + if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { + // The grandparent does not have an entry, and the child is + // modified and being pruned. This means we can just delete + // it from the parent. + cacheCoins.erase(itUs); + } else { + // A normal modification. + itUs->second.coins.swap(it->second.coins); + itUs->second.flags |= CCoinsCacheEntry::DIRTY; + } + } + } CCoinsMap::iterator itOld = it++; mapCoins.erase(itOld); } @@ -212,8 +247,12 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_) : cache(cache_), it(it_) {} -CCoinsModifier::~CCoinsModifier() { +CCoinsModifier::~CCoinsModifier() +{ assert(cache.hasModifier); cache.hasModifier = false; - it->second.Cleanup(); + it->second.coins.Cleanup(); + if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { + cache.cacheCoins.erase(it); + } } diff --git a/src/coins.h b/src/coins.h index ce7a79740..71aea79ad 100644 --- a/src/coins.h +++ b/src/coins.h @@ -271,7 +271,20 @@ public: } }; -typedef boost::unordered_map CCoinsMap; +struct CCoinsCacheEntry +{ + CCoins coins; // The actual cached data. + unsigned char flags; + + enum Flags { + DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view. + FRESH = (1 << 1), // The parent view does not have this entry (or it is pruned). + }; + + CCoinsCacheEntry() : coins(), flags(0) {} +}; + +typedef boost::unordered_map CCoinsMap; struct CCoinsStats { @@ -343,8 +356,8 @@ private: CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_); public: - CCoins* operator->() { return &it->second; } - CCoins& operator*() { return it->second; } + CCoins* operator->() { return &it->second.coins; } + CCoins& operator*() { return it->second.coins; } ~CCoinsModifier(); friend class CCoinsViewCache; }; diff --git a/src/main.cpp b/src/main.cpp index af8810ebd..fbe63411d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1797,10 +1797,10 @@ void static UpdateTip(CBlockIndex *pindexNew) { nTimeBestReceived = GetTime(); mempool.AddTransactionsUpdated(1); - LogPrintf("UpdateTip: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f\n", + LogPrintf("UpdateTip: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f cache=%u\n", chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx, DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), - Checkpoints::GuessVerificationProgress(chainActive.Tip())); + Checkpoints::GuessVerificationProgress(chainActive.Tip()), (unsigned int)pcoinsTip->GetCacheSize()); cvBlockChange.notify_all(); diff --git a/src/txdb.cpp b/src/txdb.cpp index 3b353ab62..89830ced7 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -45,17 +45,22 @@ uint256 CCoinsViewDB::GetBestBlock() const { } bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { - LogPrint("coindb", "Committing %u changed transactions to coin database...\n", (unsigned int)mapCoins.size()); - CLevelDBBatch batch; + size_t count = 0; + size_t changed = 0; for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { - BatchWriteCoins(batch, it->first, it->second); + if (it->second.flags & CCoinsCacheEntry::DIRTY) { + BatchWriteCoins(batch, it->first, it->second.coins); + changed++; + } + count++; CCoinsMap::iterator itOld = it++; mapCoins.erase(itOld); } if (hashBlock != uint256(0)) BatchWriteHashBestChain(batch, hashBlock); + LogPrint("coindb", "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); return db.WriteBatch(batch); }