Keep track of memory usage in CCoinsViewCache
This commit is contained in:
parent
540629c6fb
commit
046392dc1d
3 changed files with 64 additions and 7 deletions
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "coins.h"
|
||||
|
||||
#include "memusage.h"
|
||||
#include "random.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
@ -57,13 +58,17 @@ bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStat
|
|||
|
||||
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
|
||||
|
||||
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false) { }
|
||||
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), cachedCoinsUsage(0) { }
|
||||
|
||||
CCoinsViewCache::~CCoinsViewCache()
|
||||
{
|
||||
assert(!hasModifier);
|
||||
}
|
||||
|
||||
size_t CCoinsViewCache::DynamicMemoryUsage() const {
|
||||
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
|
||||
}
|
||||
|
||||
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
|
||||
CCoinsMap::iterator it = cacheCoins.find(txid);
|
||||
if (it != cacheCoins.end())
|
||||
|
@ -78,6 +83,7 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const
|
|||
// version as fresh.
|
||||
ret->second.flags = CCoinsCacheEntry::FRESH;
|
||||
}
|
||||
cachedCoinsUsage += memusage::DynamicUsage(ret->second.coins);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -93,6 +99,7 @@ bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
|
|||
CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
|
||||
assert(!hasModifier);
|
||||
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry()));
|
||||
size_t cachedCoinUsage = 0;
|
||||
if (ret.second) {
|
||||
if (!base->GetCoins(txid, ret.first->second.coins)) {
|
||||
// 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.
|
||||
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.
|
||||
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 {
|
||||
|
@ -150,6 +159,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
|
|||
assert(it->second.flags & CCoinsCacheEntry::FRESH);
|
||||
CCoinsCacheEntry& entry = cacheCoins[it->first];
|
||||
entry.coins.swap(it->second.coins);
|
||||
cachedCoinsUsage += memusage::DynamicUsage(entry.coins);
|
||||
entry.flags = CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH;
|
||||
}
|
||||
} else {
|
||||
|
@ -157,10 +167,13 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
|
|||
// 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.
|
||||
cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins);
|
||||
cacheCoins.erase(itUs);
|
||||
} else {
|
||||
// A normal modification.
|
||||
cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins);
|
||||
itUs->second.coins.swap(it->second.coins);
|
||||
cachedCoinsUsage += memusage::DynamicUsage(itUs->second.coins);
|
||||
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +188,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
|
|||
bool CCoinsViewCache::Flush() {
|
||||
bool fOk = base->BatchWrite(cacheCoins, hashBlock);
|
||||
cacheCoins.clear();
|
||||
cachedCoinsUsage = 0;
|
||||
return fOk;
|
||||
}
|
||||
|
||||
|
@ -232,7 +246,7 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const
|
|||
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);
|
||||
cache.hasModifier = true;
|
||||
}
|
||||
|
@ -242,7 +256,11 @@ CCoinsModifier::~CCoinsModifier()
|
|||
assert(cache.hasModifier);
|
||||
cache.hasModifier = false;
|
||||
it->second.coins.Cleanup();
|
||||
cache.cachedCoinsUsage -= cachedCoinUsage; // Subtract the old usage
|
||||
if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
|
||||
cache.cacheCoins.erase(it);
|
||||
} else {
|
||||
// If the coin still exists after the modification, add the new usage
|
||||
cache.cachedCoinsUsage += memusage::DynamicUsage(it->second.coins);
|
||||
}
|
||||
}
|
||||
|
|
20
src/coins.h
20
src/coins.h
|
@ -7,6 +7,7 @@
|
|||
#define BITCOIN_COINS_H
|
||||
|
||||
#include "compressor.h"
|
||||
#include "memusage.h"
|
||||
#include "serialize.h"
|
||||
#include "uint256.h"
|
||||
|
||||
|
@ -252,6 +253,15 @@ public:
|
|||
return false;
|
||||
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
|
||||
|
@ -356,7 +366,8 @@ class CCoinsModifier
|
|||
private:
|
||||
CCoinsViewCache& cache;
|
||||
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:
|
||||
CCoins* operator->() { return &it->second.coins; }
|
||||
|
@ -372,6 +383,7 @@ protected:
|
|||
/* Whether this cache has an active modifier. */
|
||||
bool hasModifier;
|
||||
|
||||
|
||||
/**
|
||||
* Make mutable so that we can "fill the cache" even from Get-methods
|
||||
* declared as "const".
|
||||
|
@ -379,6 +391,9 @@ protected:
|
|||
mutable uint256 hashBlock;
|
||||
mutable CCoinsMap cacheCoins;
|
||||
|
||||
/* Cached dynamic memory usage for the inner CCoins objects. */
|
||||
mutable size_t cachedCoinsUsage;
|
||||
|
||||
public:
|
||||
CCoinsViewCache(CCoinsView *baseIn);
|
||||
~CCoinsViewCache();
|
||||
|
@ -414,6 +429,9 @@ public:
|
|||
//! Calculate the size of the cache (in number of transactions)
|
||||
unsigned int GetCacheSize() const;
|
||||
|
||||
//! Calculate the size of the cache (in bytes)
|
||||
size_t DynamicMemoryUsage() const;
|
||||
|
||||
/**
|
||||
* Amount of bitcoins coming in to a transaction
|
||||
* Note that lightweight clients may not know anything besides the hash of previous transactions,
|
||||
|
|
|
@ -59,6 +59,24 @@ public:
|
|||
|
||||
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)
|
||||
|
@ -90,8 +108,8 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|||
|
||||
// The cache stack.
|
||||
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
|
||||
std::vector<CCoinsViewCache*> stack; // A stack of CCoinsViewCaches on top.
|
||||
stack.push_back(new CCoinsViewCache(&base)); // Start with one cache.
|
||||
std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
|
||||
stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
|
||||
|
||||
// Use a limited set of random transaction ids, so we do test overwriting entries.
|
||||
std::vector<uint256> txids;
|
||||
|
@ -136,6 +154,9 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|||
missed_an_entry = true;
|
||||
}
|
||||
}
|
||||
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
|
||||
test->SelfTest();
|
||||
}
|
||||
}
|
||||
|
||||
if (insecure_rand() % 100 == 0) {
|
||||
|
@ -152,7 +173,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|||
} else {
|
||||
removed_all_caches = true;
|
||||
}
|
||||
stack.push_back(new CCoinsViewCache(tip));
|
||||
stack.push_back(new CCoinsViewCacheTest(tip));
|
||||
if (stack.size() == 4) {
|
||||
reached_4_caches = true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue