Merge pull request #6102

86a5f4b Relocate calls to CheckDiskSpace (Alex Morcos)
67708ac Write block index more frequently than cache flushes (Pieter Wuille)
b3ed423 Cache tweak and logging improvements (Pieter Wuille)
fc684ad Use accurate memory for flushing decisions (Pieter Wuille)
046392d Keep track of memory usage in CCoinsViewCache (Pieter Wuille)
540629c Add memusage.h (Pieter Wuille)
This commit is contained in:
Wladimir J. van der Laan 2015-05-15 13:39:31 +02:00
commit 6fb90d8983
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
9 changed files with 243 additions and 42 deletions

View File

@ -100,6 +100,7 @@ BITCOIN_CORE_H = \
leveldbwrapper.h \ leveldbwrapper.h \
limitedmap.h \ limitedmap.h \
main.h \ main.h \
memusage.h \
merkleblock.h \ merkleblock.h \
miner.h \ miner.h \
mruset.h \ mruset.h \

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

@ -1061,18 +1061,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
} }
// cache size calculations // cache size calculations
size_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20); int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
if (nTotalCache < (nMinDbCache << 20)) nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
nTotalCache = (nMinDbCache << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache
else if (nTotalCache > (nMaxDbCache << 20)) int64_t nBlockTreeDBCache = nTotalCache / 8;
nTotalCache = (nMaxDbCache << 20); // total cache cannot be greater than nMaxDbCache
size_t nBlockTreeDBCache = nTotalCache / 8;
if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false))
nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB
nTotalCache -= nBlockTreeDBCache; nTotalCache -= nBlockTreeDBCache;
size_t nCoinDBCache = nTotalCache / 2; // use half of the remaining cache for coindb cache int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
nTotalCache -= nCoinDBCache; nTotalCache -= nCoinDBCache;
nCoinCacheSize = nTotalCache / 300; // coins in memory require around 300 bytes nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
LogPrintf("Cache configuration:\n");
LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024));
LogPrintf("* Using %.1fMiB for in-memory UTXO set\n", nCoinCacheUsage * (1.0 / 1024 / 1024));
bool fLoaded = false; bool fLoaded = false;
while (!fLoaded) { while (!fLoaded) {

View File

@ -57,7 +57,7 @@ bool fPruneMode = false;
bool fIsBareMultisigStd = true; bool fIsBareMultisigStd = true;
bool fCheckBlockIndex = false; bool fCheckBlockIndex = false;
bool fCheckpointsEnabled = true; bool fCheckpointsEnabled = true;
unsigned int nCoinCacheSize = 5000; size_t nCoinCacheUsage = 5000 * 300;
uint64_t nPruneTarget = 0; uint64_t nPruneTarget = 0;
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */ /** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
@ -1880,6 +1880,8 @@ enum FlushStateMode {
bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) { bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
LOCK2(cs_main, cs_LastBlockFile); LOCK2(cs_main, cs_LastBlockFile);
static int64_t nLastWrite = 0; static int64_t nLastWrite = 0;
static int64_t nLastFlush = 0;
static int64_t nLastSetChain = 0;
std::set<int> setFilesToPrune; std::set<int> setFilesToPrune;
bool fFlushForPrune = false; bool fFlushForPrune = false;
try { try {
@ -1893,16 +1895,32 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
} }
} }
} }
if ((mode == FLUSH_STATE_ALWAYS) || int64_t nNow = GetTimeMicros();
((mode == FLUSH_STATE_PERIODIC || mode == FLUSH_STATE_IF_NEEDED) && pcoinsTip->GetCacheSize() > nCoinCacheSize) || // Avoid writing/flushing immediately after startup.
(mode == FLUSH_STATE_PERIODIC && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000) || if (nLastWrite == 0) {
fFlushForPrune) { nLastWrite = nNow;
// Typical CCoins structures on disk are around 100 bytes in size. }
// Pushing a new one to the database can cause it to be written if (nLastFlush == 0) {
// twice (once in the log, and once in the tables). This is already nLastFlush = nNow;
// an overestimation, as most will delete an existing entry or }
// overwrite one. Still, use a conservative safety factor of 2. if (nLastSetChain == 0) {
if (!CheckDiskSpace(100 * 2 * 2 * pcoinsTip->GetCacheSize())) nLastSetChain = nNow;
}
size_t cacheSize = pcoinsTip->DynamicMemoryUsage();
// The cache is large and close to the limit, but we have time now (not in the middle of a block processing).
bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize * (10.0/9) > nCoinCacheUsage;
// The cache is over the limit, we have to write now.
bool fCacheCritical = mode == FLUSH_STATE_IF_NEEDED && cacheSize > nCoinCacheUsage;
// It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash.
bool fPeriodicWrite = mode == FLUSH_STATE_PERIODIC && nNow > nLastWrite + (int64_t)DATABASE_WRITE_INTERVAL * 1000000;
// It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage.
bool fPeriodicFlush = mode == FLUSH_STATE_PERIODIC && nNow > nLastFlush + (int64_t)DATABASE_FLUSH_INTERVAL * 1000000;
// Combine all conditions that result in a full cache flush.
bool fDoFullFlush = (mode == FLUSH_STATE_ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicFlush || fFlushForPrune;
// Write blocks and block index to disk.
if (fDoFullFlush || fPeriodicWrite) {
// Depend on nMinDiskSpace to ensure we can write block index
if (!CheckDiskSpace(0))
return state.Error("out of disk space"); return state.Error("out of disk space");
// First make sure all block and undo data is flushed to disk. // First make sure all block and undo data is flushed to disk.
FlushBlockFile(); FlushBlockFile();
@ -1924,21 +1942,31 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
return state.Abort("Files to write to block index database"); return state.Abort("Files to write to block index database");
} }
} }
// Flush the chainstate (which may refer to block index entries).
if (!pcoinsTip->Flush())
return state.Abort("Failed to write to coin database");
// Finally remove any pruned files // Finally remove any pruned files
if (fFlushForPrune) { if (fFlushForPrune) {
UnlinkPrunedFiles(setFilesToPrune); UnlinkPrunedFiles(setFilesToPrune);
fCheckForPruning = false; fCheckForPruning = false;
} }
nLastWrite = nNow;
}
// Flush best chain related state. This can only be done if the blocks / block index write was also done.
if (fDoFullFlush) {
// Typical CCoins structures on disk are around 128 bytes in size.
// Pushing a new one to the database can cause it to be written
// twice (once in the log, and once in the tables). This is already
// an overestimation, as most will delete an existing entry or
// overwrite one. Still, use a conservative safety factor of 2.
if (!CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize()))
return state.Error("out of disk space");
// Flush the chainstate (which may refer to block index entries).
if (!pcoinsTip->Flush())
return state.Abort("Failed to write to coin database");
nLastFlush = nNow;
}
if ((mode == FLUSH_STATE_ALWAYS || mode == FLUSH_STATE_PERIODIC) && nNow > nLastSetChain + (int64_t)DATABASE_WRITE_INTERVAL * 1000000) {
// Update best block in wallet (so we can detect restored wallets). // Update best block in wallet (so we can detect restored wallets).
if (mode != FLUSH_STATE_IF_NEEDED) { GetMainSignals().SetBestChain(chainActive.GetLocator());
GetMainSignals().SetBestChain(chainActive.GetLocator()); nLastSetChain = nNow;
}
nLastWrite = GetTimeMicros();
} }
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
return state.Abort(std::string("System error while flushing: ") + e.what()); return state.Abort(std::string("System error while flushing: ") + e.what());
@ -1966,10 +1994,10 @@ void static UpdateTip(CBlockIndex *pindexNew) {
nTimeBestReceived = GetTime(); nTimeBestReceived = GetTime();
mempool.AddTransactionsUpdated(1); mempool.AddTransactionsUpdated(1);
LogPrintf("%s: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f cache=%u\n", __func__, LogPrintf("%s: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f cache=%.1fMiB(%utx)\n", __func__,
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx, 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()), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip()), (unsigned int)pcoinsTip->GetCacheSize()); Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip()), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize());
cvBlockChange.notify_all(); cvBlockChange.notify_all();
@ -3197,7 +3225,7 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth
} }
} }
// check level 3: check for inconsistencies during memory-only disconnect of tip blocks // check level 3: check for inconsistencies during memory-only disconnect of tip blocks
if (nCheckLevel >= 3 && pindex == pindexState && (coins.GetCacheSize() + pcoinsTip->GetCacheSize()) <= nCoinCacheSize) { if (nCheckLevel >= 3 && pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) {
bool fClean = true; bool fClean = true;
if (!DisconnectBlock(block, state, pindex, coins, &fClean)) if (!DisconnectBlock(block, state, pindex, coins, &fClean))
return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());

View File

@ -82,8 +82,10 @@ static const unsigned int MAX_HEADERS_RESULTS = 2000;
* degree of disordering of blocks on disk (which make reindexing and in the future perhaps pruning * degree of disordering of blocks on disk (which make reindexing and in the future perhaps pruning
* harder). We'll probably want to make this a per-peer adaptive value at some point. */ * harder). We'll probably want to make this a per-peer adaptive value at some point. */
static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024; static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024;
/** Time to wait (in seconds) between writing blockchain state to disk. */ /** Time to wait (in seconds) between writing blocks/block index to disk. */
static const unsigned int DATABASE_WRITE_INTERVAL = 3600; static const unsigned int DATABASE_WRITE_INTERVAL = 60 * 60;
/** Time to wait (in seconds) between flushing chainstate to disk. */
static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60;
/** Maximum length of reject messages. */ /** Maximum length of reject messages. */
static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111; static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111;
@ -119,7 +121,7 @@ extern bool fTxIndex;
extern bool fIsBareMultisigStd; extern bool fIsBareMultisigStd;
extern bool fCheckBlockIndex; extern bool fCheckBlockIndex;
extern bool fCheckpointsEnabled; extern bool fCheckpointsEnabled;
extern unsigned int nCoinCacheSize; extern size_t nCoinCacheUsage;
extern CFeeRate minRelayTxFee; extern CFeeRate minRelayTxFee;
/** Best header we've seen so far (used for getheaders queries' starting points). */ /** Best header we've seen so far (used for getheaders queries' starting points). */

111
src/memusage.h Normal file
View File

@ -0,0 +1,111 @@
// Copyright (c) 2015 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_MEMUSAGE_H
#define BITCOIN_MEMUSAGE_H
#include <stdlib.h>
#include <map>
#include <set>
#include <vector>
#include <boost/unordered_set.hpp>
#include <boost/unordered_map.hpp>
namespace memusage
{
/** Compute the total memory used by allocating alloc bytes. */
static size_t MallocUsage(size_t alloc);
/** Compute the memory used for dynamically allocated but owned data structures.
* For generic data types, this is *not* recursive. DynamicUsage(vector<vector<int> >)
* will compute the memory used for the vector<int>'s, but not for the ints inside.
* This is for efficiency reasons, as these functions are intended to be fast. If
* application data structures require more accurate inner accounting, they should
* do the recursion themselves, or use more efficient caching + updating on modification.
*/
template<typename X> static size_t DynamicUsage(const std::vector<X>& v);
template<typename X> static size_t DynamicUsage(const std::set<X>& s);
template<typename X, typename Y> static size_t DynamicUsage(const std::map<X, Y>& m);
template<typename X, typename Y> static size_t DynamicUsage(const boost::unordered_set<X, Y>& s);
template<typename X, typename Y, typename Z> static size_t DynamicUsage(const boost::unordered_map<X, Y, Z>& s);
template<typename X> static size_t DynamicUsage(const X& x);
static inline size_t MallocUsage(size_t alloc)
{
// Measured on libc6 2.19 on Linux.
if (sizeof(void*) == 8) {
return ((alloc + 31) >> 4) << 4;
} else if (sizeof(void*) == 4) {
return ((alloc + 15) >> 3) << 3;
} else {
assert(0);
}
}
// STL data structures
template<typename X>
struct stl_tree_node
{
private:
int color;
void* parent;
void* left;
void* right;
X x;
};
template<typename X>
static inline size_t DynamicUsage(const std::vector<X>& v)
{
return MallocUsage(v.capacity() * sizeof(X));
}
template<typename X>
static inline size_t DynamicUsage(const std::set<X>& s)
{
return MallocUsage(sizeof(stl_tree_node<X>)) * s.size();
}
template<typename X, typename Y>
static inline size_t DynamicUsage(const std::map<X, Y>& m)
{
return MallocUsage(sizeof(stl_tree_node<std::pair<const X, Y> >)) * m.size();
}
// Boost data structures
template<typename X>
struct boost_unordered_node : private X
{
private:
void* ptr;
};
template<typename X, typename Y>
static inline size_t DynamicUsage(const boost::unordered_set<X, Y>& s)
{
return MallocUsage(sizeof(boost_unordered_node<X>)) * s.size() + MallocUsage(sizeof(void*) * s.bucket_count());
}
template<typename X, typename Y, typename Z>
static inline size_t DynamicUsage(const boost::unordered_map<X, Y, Z>& m)
{
return MallocUsage(sizeof(boost_unordered_node<std::pair<const X, Y> >)) * m.size() + MallocUsage(sizeof(void*) * m.bucket_count());
}
// Dispatch to class method as fallback
template<typename X>
static inline size_t DynamicUsage(const X& x)
{
return x.DynamicMemoryUsage();
}
}
#endif

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;
} }

View File

@ -22,7 +22,7 @@ class uint256;
//! -dbcache default (MiB) //! -dbcache default (MiB)
static const int64_t nDefaultDbCache = 100; static const int64_t nDefaultDbCache = 100;
//! max. -dbcache in (MiB) //! max. -dbcache in (MiB)
static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 4096 : 1024; static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024;
//! min. -dbcache in (MiB) //! min. -dbcache in (MiB)
static const int64_t nMinDbCache = 4; static const int64_t nMinDbCache = 4;