From 046392dc1dd965b4ec1ba60a14a714e3e3fa7a88 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 4 May 2015 01:31:11 +0200 Subject: [PATCH] Keep track of memory usage in CCoinsViewCache --- src/coins.cpp | 24 +++++++++++++++++++++--- src/coins.h | 20 +++++++++++++++++++- src/test/coins_tests.cpp | 27 ++++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index d79e29951..a41d5a310 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -4,6 +4,7 @@ #include "coins.h" +#include "memusage.h" #include "random.h" #include @@ -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 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); } } diff --git a/src/coins.h b/src/coins.h index fe2eaa08e..a4671645d 100644 --- a/src/coins.h +++ b/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 *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, diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 2e2cc2214..34b311b80 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -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 stack; // A stack of CCoinsViewCaches on top. - stack.push_back(new CCoinsViewCache(&base)); // Start with one cache. + std::vector 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 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; }