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 "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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
src/coins.h
20
src/coins.h
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue