Skip to content

Commit

Permalink
Merge pull request bitcoin#6102
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
laanwj committed May 15, 2015
2 parents 63e7016 + 86a5f4b commit 6fb90d8
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 42 deletions.
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ BITCOIN_CORE_H = \
leveldbwrapper.h \
limitedmap.h \
main.h \
memusage.h \
merkleblock.h \
miner.h \
mruset.h \
Expand Down
24 changes: 21 additions & 3 deletions src/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "coins.h"

#include "memusage.h"
#include "random.h"

#include <assert.h>
Expand Down Expand Up @@ -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())
Expand All @@ -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;
}

Expand All @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -150,17 +159,21 @@ 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 {
if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
// 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;
}
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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 changes: 19 additions & 1 deletion src/coins.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define BITCOIN_COINS_H

#include "compressor.h"
#include "memusage.h"
#include "serialize.h"
#include "uint256.h"

Expand Down Expand Up @@ -252,6 +253,15 @@ class CCoins
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
Expand Down Expand Up @@ -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; }
Expand All @@ -372,13 +383,17 @@ class CCoinsViewCache : public CCoinsViewBacked
/* 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".
*/
mutable uint256 hashBlock;
mutable CCoinsMap cacheCoins;

/* Cached dynamic memory usage for the inner CCoins objects. */
mutable size_t cachedCoinsUsage;

public:
CCoinsViewCache(CCoinsView *baseIn);
~CCoinsViewCache();
Expand Down Expand Up @@ -414,6 +429,9 @@ class CCoinsViewCache : public CCoinsViewBacked
//! 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,
Expand Down
18 changes: 10 additions & 8 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1061,18 +1061,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
}

// cache size calculations
size_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
if (nTotalCache < (nMinDbCache << 20))
nTotalCache = (nMinDbCache << 20); // total cache cannot be less than nMinDbCache
else if (nTotalCache > (nMaxDbCache << 20))
nTotalCache = (nMaxDbCache << 20); // total cache cannot be greater than nMaxDbCache
size_t nBlockTreeDBCache = nTotalCache / 8;
int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache
int64_t nBlockTreeDBCache = nTotalCache / 8;
if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false))
nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB
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;
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;
while (!fLoaded) {
Expand Down
74 changes: 51 additions & 23 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ bool fPruneMode = false;
bool fIsBareMultisigStd = true;
bool fCheckBlockIndex = false;
bool fCheckpointsEnabled = true;
unsigned int nCoinCacheSize = 5000;
size_t nCoinCacheUsage = 5000 * 300;
uint64_t nPruneTarget = 0;

/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
Expand Down Expand Up @@ -1880,6 +1880,8 @@ enum FlushStateMode {
bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
LOCK2(cs_main, cs_LastBlockFile);
static int64_t nLastWrite = 0;
static int64_t nLastFlush = 0;
static int64_t nLastSetChain = 0;
std::set<int> setFilesToPrune;
bool fFlushForPrune = false;
try {
Expand All @@ -1893,16 +1895,32 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
}
}
}
if ((mode == FLUSH_STATE_ALWAYS) ||
((mode == FLUSH_STATE_PERIODIC || mode == FLUSH_STATE_IF_NEEDED) && pcoinsTip->GetCacheSize() > nCoinCacheSize) ||
(mode == FLUSH_STATE_PERIODIC && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000) ||
fFlushForPrune) {
// Typical CCoins structures on disk are around 100 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(100 * 2 * 2 * pcoinsTip->GetCacheSize()))
int64_t nNow = GetTimeMicros();
// Avoid writing/flushing immediately after startup.
if (nLastWrite == 0) {
nLastWrite = nNow;
}
if (nLastFlush == 0) {
nLastFlush = nNow;
}
if (nLastSetChain == 0) {
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");
// First make sure all block and undo data is flushed to disk.
FlushBlockFile();
Expand All @@ -1924,21 +1942,31 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
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
if (fFlushForPrune) {
UnlinkPrunedFiles(setFilesToPrune);
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).
if (mode != FLUSH_STATE_IF_NEEDED) {
GetMainSignals().SetBestChain(chainActive.GetLocator());
}
nLastWrite = GetTimeMicros();
GetMainSignals().SetBestChain(chainActive.GetLocator());
nLastSetChain = nNow;
}
} catch (const std::runtime_error& e) {
return state.Abort(std::string("System error while flushing: ") + e.what());
Expand Down Expand Up @@ -1966,10 +1994,10 @@ void static UpdateTip(CBlockIndex *pindexNew) {
nTimeBestReceived = GetTime();
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,
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();

Expand Down Expand Up @@ -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
if (nCheckLevel >= 3 && pindex == pindexState && (coins.GetCacheSize() + pcoinsTip->GetCacheSize()) <= nCoinCacheSize) {
if (nCheckLevel >= 3 && pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) {
bool fClean = true;
if (!DisconnectBlock(block, state, pindex, coins, &fClean))
return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
Expand Down
8 changes: 5 additions & 3 deletions src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
* harder). We'll probably want to make this a per-peer adaptive value at some point. */
static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024;
/** Time to wait (in seconds) between writing blockchain state to disk. */
static const unsigned int DATABASE_WRITE_INTERVAL = 3600;
/** Time to wait (in seconds) between writing blocks/block index to disk. */
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. */
static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111;

Expand Down Expand Up @@ -119,7 +121,7 @@ extern bool fTxIndex;
extern bool fIsBareMultisigStd;
extern bool fCheckBlockIndex;
extern bool fCheckpointsEnabled;
extern unsigned int nCoinCacheSize;
extern size_t nCoinCacheUsage;
extern CFeeRate minRelayTxFee;

/** Best header we've seen so far (used for getheaders queries' starting points). */
Expand Down
Loading

0 comments on commit 6fb90d8

Please sign in to comment.