Skip to content

Commit 6fb90d8

Browse files
committed
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)
2 parents 63e7016 + 86a5f4b commit 6fb90d8

File tree

9 files changed

+243
-42
lines changed

9 files changed

+243
-42
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ BITCOIN_CORE_H = \
100100
leveldbwrapper.h \
101101
limitedmap.h \
102102
main.h \
103+
memusage.h \
103104
merkleblock.h \
104105
miner.h \
105106
mruset.h \

src/coins.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "coins.h"
66

7+
#include "memusage.h"
78
#include "random.h"
89

910
#include <assert.h>
@@ -57,13 +58,17 @@ bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStat
5758

5859
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
5960

60-
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false) { }
61+
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), cachedCoinsUsage(0) { }
6162

6263
CCoinsViewCache::~CCoinsViewCache()
6364
{
6465
assert(!hasModifier);
6566
}
6667

68+
size_t CCoinsViewCache::DynamicMemoryUsage() const {
69+
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
70+
}
71+
6772
CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
6873
CCoinsMap::iterator it = cacheCoins.find(txid);
6974
if (it != cacheCoins.end())
@@ -78,6 +83,7 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const
7883
// version as fresh.
7984
ret->second.flags = CCoinsCacheEntry::FRESH;
8085
}
86+
cachedCoinsUsage += memusage::DynamicUsage(ret->second.coins);
8187
return ret;
8288
}
8389

@@ -93,6 +99,7 @@ bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
9399
CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
94100
assert(!hasModifier);
95101
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry()));
102+
size_t cachedCoinUsage = 0;
96103
if (ret.second) {
97104
if (!base->GetCoins(txid, ret.first->second.coins)) {
98105
// The parent view does not have this entry; mark it as fresh.
@@ -102,10 +109,12 @@ CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
102109
// The parent view only has a pruned entry for this; mark it as fresh.
103110
ret.first->second.flags = CCoinsCacheEntry::FRESH;
104111
}
112+
} else {
113+
cachedCoinUsage = memusage::DynamicUsage(ret.first->second.coins);
105114
}
106115
// Assume that whenever ModifyCoins is called, the entry will be modified.
107116
ret.first->second.flags |= CCoinsCacheEntry::DIRTY;
108-
return CCoinsModifier(*this, ret.first);
117+
return CCoinsModifier(*this, ret.first, cachedCoinUsage);
109118
}
110119

111120
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
@@ -150,17 +159,21 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
150159
assert(it->second.flags & CCoinsCacheEntry::FRESH);
151160
CCoinsCacheEntry& entry = cacheCoins[it->first];
152161
entry.coins.swap(it->second.coins);
162+
cachedCoinsUsage += memusage::DynamicUsage(entry.coins);
153163
entry.flags = CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH;
154164
}
155165
} else {
156166
if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
157167
// The grandparent does not have an entry, and the child is
158168
// modified and being pruned. This means we can just delete
159169
// it from the parent.
170+
cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins);
160171
cacheCoins.erase(itUs);
161172
} else {
162173
// A normal modification.
174+
cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins);
163175
itUs->second.coins.swap(it->second.coins);
176+
cachedCoinsUsage += memusage::DynamicUsage(itUs->second.coins);
164177
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
165178
}
166179
}
@@ -175,6 +188,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
175188
bool CCoinsViewCache::Flush() {
176189
bool fOk = base->BatchWrite(cacheCoins, hashBlock);
177190
cacheCoins.clear();
191+
cachedCoinsUsage = 0;
178192
return fOk;
179193
}
180194

@@ -232,7 +246,7 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const
232246
return tx.ComputePriority(dResult);
233247
}
234248

235-
CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_) : cache(cache_), it(it_) {
249+
CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage) : cache(cache_), it(it_), cachedCoinUsage(usage) {
236250
assert(!cache.hasModifier);
237251
cache.hasModifier = true;
238252
}
@@ -242,7 +256,11 @@ CCoinsModifier::~CCoinsModifier()
242256
assert(cache.hasModifier);
243257
cache.hasModifier = false;
244258
it->second.coins.Cleanup();
259+
cache.cachedCoinsUsage -= cachedCoinUsage; // Subtract the old usage
245260
if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) {
246261
cache.cacheCoins.erase(it);
262+
} else {
263+
// If the coin still exists after the modification, add the new usage
264+
cache.cachedCoinsUsage += memusage::DynamicUsage(it->second.coins);
247265
}
248266
}

src/coins.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define BITCOIN_COINS_H
88

99
#include "compressor.h"
10+
#include "memusage.h"
1011
#include "serialize.h"
1112
#include "uint256.h"
1213

@@ -252,6 +253,15 @@ class CCoins
252253
return false;
253254
return true;
254255
}
256+
257+
size_t DynamicMemoryUsage() const {
258+
size_t ret = memusage::DynamicUsage(vout);
259+
BOOST_FOREACH(const CTxOut &out, vout) {
260+
const std::vector<unsigned char> *script = &out.scriptPubKey;
261+
ret += memusage::DynamicUsage(*script);
262+
}
263+
return ret;
264+
}
255265
};
256266

257267
class CCoinsKeyHasher
@@ -356,7 +366,8 @@ class CCoinsModifier
356366
private:
357367
CCoinsViewCache& cache;
358368
CCoinsMap::iterator it;
359-
CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_);
369+
size_t cachedCoinUsage; // Cached memory usage of the CCoins object before modification
370+
CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage);
360371

361372
public:
362373
CCoins* operator->() { return &it->second.coins; }
@@ -372,13 +383,17 @@ class CCoinsViewCache : public CCoinsViewBacked
372383
/* Whether this cache has an active modifier. */
373384
bool hasModifier;
374385

386+
375387
/**
376388
* Make mutable so that we can "fill the cache" even from Get-methods
377389
* declared as "const".
378390
*/
379391
mutable uint256 hashBlock;
380392
mutable CCoinsMap cacheCoins;
381393

394+
/* Cached dynamic memory usage for the inner CCoins objects. */
395+
mutable size_t cachedCoinsUsage;
396+
382397
public:
383398
CCoinsViewCache(CCoinsView *baseIn);
384399
~CCoinsViewCache();
@@ -414,6 +429,9 @@ class CCoinsViewCache : public CCoinsViewBacked
414429
//! Calculate the size of the cache (in number of transactions)
415430
unsigned int GetCacheSize() const;
416431

432+
//! Calculate the size of the cache (in bytes)
433+
size_t DynamicMemoryUsage() const;
434+
417435
/**
418436
* Amount of bitcoins coming in to a transaction
419437
* Note that lightweight clients may not know anything besides the hash of previous transactions,

src/init.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,18 +1061,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
10611061
}
10621062

10631063
// cache size calculations
1064-
size_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
1065-
if (nTotalCache < (nMinDbCache << 20))
1066-
nTotalCache = (nMinDbCache << 20); // total cache cannot be less than nMinDbCache
1067-
else if (nTotalCache > (nMaxDbCache << 20))
1068-
nTotalCache = (nMaxDbCache << 20); // total cache cannot be greater than nMaxDbCache
1069-
size_t nBlockTreeDBCache = nTotalCache / 8;
1064+
int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
1065+
nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
1066+
nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache
1067+
int64_t nBlockTreeDBCache = nTotalCache / 8;
10701068
if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false))
10711069
nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB
10721070
nTotalCache -= nBlockTreeDBCache;
1073-
size_t nCoinDBCache = nTotalCache / 2; // use half of the remaining cache for coindb cache
1071+
int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
10741072
nTotalCache -= nCoinDBCache;
1075-
nCoinCacheSize = nTotalCache / 300; // coins in memory require around 300 bytes
1073+
nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
1074+
LogPrintf("Cache configuration:\n");
1075+
LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
1076+
LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024));
1077+
LogPrintf("* Using %.1fMiB for in-memory UTXO set\n", nCoinCacheUsage * (1.0 / 1024 / 1024));
10761078

10771079
bool fLoaded = false;
10781080
while (!fLoaded) {

src/main.cpp

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ bool fPruneMode = false;
5757
bool fIsBareMultisigStd = true;
5858
bool fCheckBlockIndex = false;
5959
bool fCheckpointsEnabled = true;
60-
unsigned int nCoinCacheSize = 5000;
60+
size_t nCoinCacheUsage = 5000 * 300;
6161
uint64_t nPruneTarget = 0;
6262

6363
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
@@ -1880,6 +1880,8 @@ enum FlushStateMode {
18801880
bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
18811881
LOCK2(cs_main, cs_LastBlockFile);
18821882
static int64_t nLastWrite = 0;
1883+
static int64_t nLastFlush = 0;
1884+
static int64_t nLastSetChain = 0;
18831885
std::set<int> setFilesToPrune;
18841886
bool fFlushForPrune = false;
18851887
try {
@@ -1893,16 +1895,32 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
18931895
}
18941896
}
18951897
}
1896-
if ((mode == FLUSH_STATE_ALWAYS) ||
1897-
((mode == FLUSH_STATE_PERIODIC || mode == FLUSH_STATE_IF_NEEDED) && pcoinsTip->GetCacheSize() > nCoinCacheSize) ||
1898-
(mode == FLUSH_STATE_PERIODIC && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000) ||
1899-
fFlushForPrune) {
1900-
// Typical CCoins structures on disk are around 100 bytes in size.
1901-
// Pushing a new one to the database can cause it to be written
1902-
// twice (once in the log, and once in the tables). This is already
1903-
// an overestimation, as most will delete an existing entry or
1904-
// overwrite one. Still, use a conservative safety factor of 2.
1905-
if (!CheckDiskSpace(100 * 2 * 2 * pcoinsTip->GetCacheSize()))
1898+
int64_t nNow = GetTimeMicros();
1899+
// Avoid writing/flushing immediately after startup.
1900+
if (nLastWrite == 0) {
1901+
nLastWrite = nNow;
1902+
}
1903+
if (nLastFlush == 0) {
1904+
nLastFlush = nNow;
1905+
}
1906+
if (nLastSetChain == 0) {
1907+
nLastSetChain = nNow;
1908+
}
1909+
size_t cacheSize = pcoinsTip->DynamicMemoryUsage();
1910+
// The cache is large and close to the limit, but we have time now (not in the middle of a block processing).
1911+
bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize * (10.0/9) > nCoinCacheUsage;
1912+
// The cache is over the limit, we have to write now.
1913+
bool fCacheCritical = mode == FLUSH_STATE_IF_NEEDED && cacheSize > nCoinCacheUsage;
1914+
// 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.
1915+
bool fPeriodicWrite = mode == FLUSH_STATE_PERIODIC && nNow > nLastWrite + (int64_t)DATABASE_WRITE_INTERVAL * 1000000;
1916+
// It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage.
1917+
bool fPeriodicFlush = mode == FLUSH_STATE_PERIODIC && nNow > nLastFlush + (int64_t)DATABASE_FLUSH_INTERVAL * 1000000;
1918+
// Combine all conditions that result in a full cache flush.
1919+
bool fDoFullFlush = (mode == FLUSH_STATE_ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicFlush || fFlushForPrune;
1920+
// Write blocks and block index to disk.
1921+
if (fDoFullFlush || fPeriodicWrite) {
1922+
// Depend on nMinDiskSpace to ensure we can write block index
1923+
if (!CheckDiskSpace(0))
19061924
return state.Error("out of disk space");
19071925
// First make sure all block and undo data is flushed to disk.
19081926
FlushBlockFile();
@@ -1924,21 +1942,31 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
19241942
return state.Abort("Files to write to block index database");
19251943
}
19261944
}
1927-
// Flush the chainstate (which may refer to block index entries).
1928-
if (!pcoinsTip->Flush())
1929-
return state.Abort("Failed to write to coin database");
1930-
19311945
// Finally remove any pruned files
19321946
if (fFlushForPrune) {
19331947
UnlinkPrunedFiles(setFilesToPrune);
19341948
fCheckForPruning = false;
19351949
}
1936-
1950+
nLastWrite = nNow;
1951+
}
1952+
// Flush best chain related state. This can only be done if the blocks / block index write was also done.
1953+
if (fDoFullFlush) {
1954+
// Typical CCoins structures on disk are around 128 bytes in size.
1955+
// Pushing a new one to the database can cause it to be written
1956+
// twice (once in the log, and once in the tables). This is already
1957+
// an overestimation, as most will delete an existing entry or
1958+
// overwrite one. Still, use a conservative safety factor of 2.
1959+
if (!CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize()))
1960+
return state.Error("out of disk space");
1961+
// Flush the chainstate (which may refer to block index entries).
1962+
if (!pcoinsTip->Flush())
1963+
return state.Abort("Failed to write to coin database");
1964+
nLastFlush = nNow;
1965+
}
1966+
if ((mode == FLUSH_STATE_ALWAYS || mode == FLUSH_STATE_PERIODIC) && nNow > nLastSetChain + (int64_t)DATABASE_WRITE_INTERVAL * 1000000) {
19371967
// Update best block in wallet (so we can detect restored wallets).
1938-
if (mode != FLUSH_STATE_IF_NEEDED) {
1939-
GetMainSignals().SetBestChain(chainActive.GetLocator());
1940-
}
1941-
nLastWrite = GetTimeMicros();
1968+
GetMainSignals().SetBestChain(chainActive.GetLocator());
1969+
nLastSetChain = nNow;
19421970
}
19431971
} catch (const std::runtime_error& e) {
19441972
return state.Abort(std::string("System error while flushing: ") + e.what());
@@ -1966,10 +1994,10 @@ void static UpdateTip(CBlockIndex *pindexNew) {
19661994
nTimeBestReceived = GetTime();
19671995
mempool.AddTransactionsUpdated(1);
19681996

1969-
LogPrintf("%s: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f cache=%u\n", __func__,
1997+
LogPrintf("%s: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f cache=%.1fMiB(%utx)\n", __func__,
19701998
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
19711999
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
1972-
Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip()), (unsigned int)pcoinsTip->GetCacheSize());
2000+
Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip()), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize());
19732001

19742002
cvBlockChange.notify_all();
19752003

@@ -3197,7 +3225,7 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth
31973225
}
31983226
}
31993227
// check level 3: check for inconsistencies during memory-only disconnect of tip blocks
3200-
if (nCheckLevel >= 3 && pindex == pindexState && (coins.GetCacheSize() + pcoinsTip->GetCacheSize()) <= nCoinCacheSize) {
3228+
if (nCheckLevel >= 3 && pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) {
32013229
bool fClean = true;
32023230
if (!DisconnectBlock(block, state, pindex, coins, &fClean))
32033231
return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());

src/main.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ static const unsigned int MAX_HEADERS_RESULTS = 2000;
8282
* degree of disordering of blocks on disk (which make reindexing and in the future perhaps pruning
8383
* harder). We'll probably want to make this a per-peer adaptive value at some point. */
8484
static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024;
85-
/** Time to wait (in seconds) between writing blockchain state to disk. */
86-
static const unsigned int DATABASE_WRITE_INTERVAL = 3600;
85+
/** Time to wait (in seconds) between writing blocks/block index to disk. */
86+
static const unsigned int DATABASE_WRITE_INTERVAL = 60 * 60;
87+
/** Time to wait (in seconds) between flushing chainstate to disk. */
88+
static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60;
8789
/** Maximum length of reject messages. */
8890
static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111;
8991

@@ -119,7 +121,7 @@ extern bool fTxIndex;
119121
extern bool fIsBareMultisigStd;
120122
extern bool fCheckBlockIndex;
121123
extern bool fCheckpointsEnabled;
122-
extern unsigned int nCoinCacheSize;
124+
extern size_t nCoinCacheUsage;
123125
extern CFeeRate minRelayTxFee;
124126

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

0 commit comments

Comments
 (0)