Keep track of memory usage in CCoinsViewCache

This commit is contained in:
Pieter Wuille 2015-05-04 01:31:11 +02:00
parent 540629c6fb
commit 046392dc1d
3 changed files with 64 additions and 7 deletions

View File

@ -4,6 +4,7 @@
#include "coins.h" #include "coins.h"
#include "memusage.h"
#include "random.h" #include "random.h"
#include <assert.h> #include <assert.h>
@ -57,13 +58,17 @@ bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStat
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false) { } CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), cachedCoinsUsage(0) { }
CCoinsViewCache::~CCoinsViewCache() CCoinsViewCache::~CCoinsViewCache()
{ {
assert(!hasModifier); assert(!hasModifier);
} }
size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
}
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
CCoinsMap::iterator it = cacheCoins.find(txid); CCoinsMap::iterator it = cacheCoins.find(txid);
if (it != cacheCoins.end()) if (it != cacheCoins.end())
@ -78,6 +83,7 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const
// version as fresh. // version as fresh.
ret->second.flags = CCoinsCacheEntry::FRESH; ret->second.flags = CCoinsCacheEntry::FRESH;
} }
cachedCoinsUsage += memusage::DynamicUsage(ret->second.coins);
return ret; return ret;
} }
@ -93,6 +99,7 @@ bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) { CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
assert(!hasModifier); assert(!hasModifier);
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry()));
size_t cachedCoinUsage = 0;
if (ret.second) { if (ret.second) {
if (!base->GetCoins(txid, ret.first->second.coins)) { if (!base->GetCoins(txid, ret.first->second.coins)) {
// The parent view does not have this entry; mark it as fresh. // The parent view does not have this entry; mark it as fresh.
@ -102,10 +109,12 @@ CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
// The parent view only has a pruned entry for this; mark it as fresh. // The parent view only has a pruned entry for this; mark it as fresh.
ret.first->second.flags = CCoinsCacheEntry::FRESH; ret.first->second.flags = CCoinsCacheEntry::FRESH;
} }
} else {
cachedCoinUsage = memusage::DynamicUsage(ret.first->second.coins);
} }
// Assume that whenever ModifyCoins is called, the entry will be modified. // Assume that whenever ModifyCoins is called, the entry will be modified.
ret.first->second.flags |= CCoinsCacheEntry::DIRTY; ret.first->second.flags |= CCoinsCacheEntry::DIRTY;
return CCoinsModifier(*this, ret.first); return CCoinsModifier(*this, ret.first, cachedCoinUsage);
} }
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const { const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
@ -150,6 +159,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
assert(it->second.flags & CCoinsCacheEntry::FRESH); assert(it->second.flags & CCoinsCacheEntry::FRESH);
CCoinsCacheEntry& entry = cacheCoins[it->first]; CCoinsCacheEntry& entry = cacheCoins[it->first];
entry.coins.swap(it->second.coins); entry.coins.swap(it->second.coins);
cachedCoinsUsage += memusage::DynamicUsage(entry.coins);
entry.flags = CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH; entry.flags = CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH;
} }
} else { } else {
@ -157,10 +167,13 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
// The grandparent does not have an entry, and the child is // The grandparent does not have an entry, and the child is
// modified and being pruned. This means we can just delete // modified and being pruned. This means we can just delete
// it from the parent. // it from the parent.
cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins);
cacheCoins.erase(itUs); cacheCoins.erase(itUs);
} else { } else {
// A normal modification. // A normal modification.
cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins);
itUs->second.coins.swap(it->second.coins); itUs->second.coins.swap(it->second.coins);
cachedCoinsUsage += memusage::DynamicUsage(itUs->second.coins);
itUs->second.flags |= CCoinsCacheEntry::DIRTY; itUs->second.flags |= CCoinsCacheEntry::DIRTY;
} }
} }
@ -175,6 +188,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
bool CCoinsViewCache::Flush() { bool CCoinsViewCache::Flush() {
bool fOk = base->BatchWrite(cacheCoins, hashBlock); bool fOk = base->BatchWrite(cacheCoins, hashBlock);
cacheCoins.clear(); cacheCoins.clear();
cachedCoinsUsage = 0;
return fOk; return fOk;
} }
@ -232,7 +246,7 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const
return tx.ComputePriority(dResult); return tx.ComputePriority(dResult);
} }
CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_) : cache(cache_), it(it_) { CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage) : cache(cache_), it(it_), cachedCoinUsage(usage) {
assert(!cache.hasModifier); assert(!cache.hasModifier);
cache.hasModifier = true; cache.hasModifier = true;
} }
@ -242,7 +256,11 @@ CCoinsModifier::~CCoinsModifier()
assert(cache.hasModifier); assert(cache.hasModifier);
cache.hasModifier = false; cache.hasModifier = false;
it->second.coins.Cleanup(); it->second.coins.Cleanup();
cache.cachedCoinsUsage -= cachedCoinUsage; // Subtract the old usage
if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
cache.cacheCoins.erase(it); cache.cacheCoins.erase(it);
} else {
// If the coin still exists after the modification, add the new usage
cache.cachedCoinsUsage += memusage::DynamicUsage(it->second.coins);
} }
} }

View File

@ -7,6 +7,7 @@
#define BITCOIN_COINS_H #define BITCOIN_COINS_H
#include "compressor.h" #include "compressor.h"
#include "memusage.h"
#include "serialize.h" #include "serialize.h"
#include "uint256.h" #include "uint256.h"
@ -252,6 +253,15 @@ public:
return false; return false;
return true; return true;
} }
size_t DynamicMemoryUsage() const {
size_t ret = memusage::DynamicUsage(vout);
BOOST_FOREACH(const CTxOut &out, vout) {
const std::vector<unsigned char> *script = &out.scriptPubKey;
ret += memusage::DynamicUsage(*script);
}
return ret;
}
}; };
class CCoinsKeyHasher class CCoinsKeyHasher
@ -356,7 +366,8 @@ class CCoinsModifier
private: private:
CCoinsViewCache& cache; CCoinsViewCache& cache;
CCoinsMap::iterator it; CCoinsMap::iterator it;
CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_); size_t cachedCoinUsage; // Cached memory usage of the CCoins object before modification
CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage);
public: public:
CCoins* operator->() { return &it->second.coins; } CCoins* operator->() { return &it->second.coins; }
@ -372,6 +383,7 @@ protected:
/* Whether this cache has an active modifier. */ /* Whether this cache has an active modifier. */
bool hasModifier; bool hasModifier;
/** /**
* Make mutable so that we can "fill the cache" even from Get-methods * Make mutable so that we can "fill the cache" even from Get-methods
* declared as "const". * declared as "const".
@ -379,6 +391,9 @@ protected:
mutable uint256 hashBlock; mutable uint256 hashBlock;
mutable CCoinsMap cacheCoins; mutable CCoinsMap cacheCoins;
/* Cached dynamic memory usage for the inner CCoins objects. */
mutable size_t cachedCoinsUsage;
public: public:
CCoinsViewCache(CCoinsView *baseIn); CCoinsViewCache(CCoinsView *baseIn);
~CCoinsViewCache(); ~CCoinsViewCache();
@ -414,6 +429,9 @@ public:
//! Calculate the size of the cache (in number of transactions) //! Calculate the size of the cache (in number of transactions)
unsigned int GetCacheSize() const; unsigned int GetCacheSize() const;
//! Calculate the size of the cache (in bytes)
size_t DynamicMemoryUsage() const;
/** /**
* Amount of bitcoins coming in to a transaction * Amount of bitcoins coming in to a transaction
* Note that lightweight clients may not know anything besides the hash of previous transactions, * Note that lightweight clients may not know anything besides the hash of previous transactions,

View File

@ -59,6 +59,24 @@ public:
bool GetStats(CCoinsStats& stats) const { return false; } bool GetStats(CCoinsStats& stats) const { return false; }
}; };
class CCoinsViewCacheTest : public CCoinsViewCache
{
public:
CCoinsViewCacheTest(CCoinsView* base) : CCoinsViewCache(base) {}
void SelfTest() const
{
// Manually recompute the dynamic usage of the whole data, and compare it.
size_t ret = memusage::DynamicUsage(cacheCoins);
for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) {
ret += memusage::DynamicUsage(it->second.coins);
}
BOOST_CHECK_EQUAL(memusage::DynamicUsage(*this), ret);
}
};
} }
BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup) BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
@ -90,8 +108,8 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
// The cache stack. // The cache stack.
CCoinsViewTest base; // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A CCoinsViewTest at the bottom.
std::vector<CCoinsViewCache*> stack; // A stack of CCoinsViewCaches on top. std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
stack.push_back(new CCoinsViewCache(&base)); // Start with one cache. stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
// Use a limited set of random transaction ids, so we do test overwriting entries. // Use a limited set of random transaction ids, so we do test overwriting entries.
std::vector<uint256> txids; std::vector<uint256> txids;
@ -136,6 +154,9 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
missed_an_entry = true; missed_an_entry = true;
} }
} }
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
test->SelfTest();
}
} }
if (insecure_rand() % 100 == 0) { if (insecure_rand() % 100 == 0) {
@ -152,7 +173,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
} else { } else {
removed_all_caches = true; removed_all_caches = true;
} }
stack.push_back(new CCoinsViewCache(tip)); stack.push_back(new CCoinsViewCacheTest(tip));
if (stack.size() == 4) { if (stack.size() == 4) {
reached_4_caches = true; reached_4_caches = true;
} }