aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPieter Wuille <pieter.wuille@gmail.com>2014-09-03 09:37:47 +0200
committerPieter Wuille <pieter.wuille@gmail.com>2014-09-23 22:29:43 +0200
commit058b08c147a6d56b57221faa5b6fcdb83b4140b2 (patch)
treec8a44779f36d1c2b7a0fe41287eeddecb4aa4e77
parentc9d1a81ce76737a73c9706e074a4fe8440c8277e (diff)
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.
-rw-r--r--src/coins.cpp85
-rw-r--r--src/coins.h19
-rw-r--r--src/main.cpp4
-rw-r--r--src/txdb.cpp11
4 files changed, 88 insertions, 31 deletions
diff --git a/src/coins.cpp b/src/coins.cpp
index 9632e67f20..9d60089bf5 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<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoins()));
+ std::pair<CCoinsMap::iterator, bool> 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 ce7a79740b..71aea79adc 100644
--- a/src/coins.h
+++ b/src/coins.h
@@ -271,7 +271,20 @@ public:
}
};
-typedef boost::unordered_map<uint256, CCoins, CCoinsKeyHasher> 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<uint256, CCoinsCacheEntry, CCoinsKeyHasher> 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 af8810ebd6..fbe63411d7 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 3b353ab624..89830ced73 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);
}