Optimisation: Store transaction list order in memory rather than compute it every need

Huge performance improvement (450%) for zapwallettxes
This commit is contained in:
Luke Dashjr 2015-10-19 09:19:38 +00:00
parent eac53ec992
commit 3e7c89196c
6 changed files with 44 additions and 52 deletions

View file

@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333333; ae.nTime = 1333333333;
ae.strOtherAccount = "b"; ae.strOtherAccount = "b";
ae.strComment = ""; ae.strComment = "";
walletdb.WriteAccountingEntry(ae); pwalletMain->AddAccountingEntry(ae, walletdb);
wtx.mapValue["comment"] = "z"; wtx.mapValue["comment"] = "z";
pwalletMain->AddToWallet(wtx, false, &walletdb); pwalletMain->AddToWallet(wtx, false, &walletdb);
@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333336; ae.nTime = 1333333336;
ae.strOtherAccount = "c"; ae.strOtherAccount = "c";
walletdb.WriteAccountingEntry(ae); pwalletMain->AddAccountingEntry(ae, walletdb);
GetResults(walletdb, results); GetResults(walletdb, results);
@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333330; ae.nTime = 1333333330;
ae.strOtherAccount = "d"; ae.strOtherAccount = "d";
ae.nOrderPos = pwalletMain->IncOrderPosNext(); ae.nOrderPos = pwalletMain->IncOrderPosNext();
walletdb.WriteAccountingEntry(ae); pwalletMain->AddAccountingEntry(ae, walletdb);
GetResults(walletdb, results); GetResults(walletdb, results);
@ -121,7 +121,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333334; ae.nTime = 1333333334;
ae.strOtherAccount = "e"; ae.strOtherAccount = "e";
ae.nOrderPos = -1; ae.nOrderPos = -1;
walletdb.WriteAccountingEntry(ae); pwalletMain->AddAccountingEntry(ae, walletdb);
GetResults(walletdb, results); GetResults(walletdb, results);

View file

@ -835,7 +835,7 @@ UniValue movecmd(const UniValue& params, bool fHelp)
debit.nTime = nNow; debit.nTime = nNow;
debit.strOtherAccount = strTo; debit.strOtherAccount = strTo;
debit.strComment = strComment; debit.strComment = strComment;
walletdb.WriteAccountingEntry(debit); pwalletMain->AddAccountingEntry(debit, walletdb);
// Credit // Credit
CAccountingEntry credit; CAccountingEntry credit;
@ -845,7 +845,7 @@ UniValue movecmd(const UniValue& params, bool fHelp)
credit.nTime = nNow; credit.nTime = nNow;
credit.strOtherAccount = strFrom; credit.strOtherAccount = strFrom;
credit.strComment = strComment; credit.strComment = strComment;
walletdb.WriteAccountingEntry(credit); pwalletMain->AddAccountingEntry(credit, walletdb);
if (!walletdb.TxnCommit()) if (!walletdb.TxnCommit())
throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); throw JSONRPCError(RPC_DATABASE_ERROR, "database error");
@ -1470,11 +1470,10 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
UniValue ret(UniValue::VARR); UniValue ret(UniValue::VARR);
std::list<CAccountingEntry> acentries; const CWallet::TxItems & txOrdered = pwalletMain->wtxOrdered;
CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount);
// iterate backwards until we have nCount items to return: // iterate backwards until we have nCount items to return:
for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{ {
CWalletTx *const pwtx = (*it).second.first; CWalletTx *const pwtx = (*it).second.first;
if (pwtx != 0) if (pwtx != 0)
@ -1579,8 +1578,7 @@ UniValue listaccounts(const UniValue& params, bool fHelp)
} }
} }
list<CAccountingEntry> acentries; const list<CAccountingEntry> & acentries = pwalletMain->laccentries;
CWalletDB(pwalletMain->strWalletFile).ListAccountCreditDebit("*", acentries);
BOOST_FOREACH(const CAccountingEntry& entry, acentries) BOOST_FOREACH(const CAccountingEntry& entry, acentries)
mapAccountBalances[entry.strAccount] += entry.nCreditDebit; mapAccountBalances[entry.strAccount] += entry.nCreditDebit;

View file

@ -588,31 +588,6 @@ int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
return nRet; return nRet;
} }
CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount)
{
AssertLockHeld(cs_wallet); // mapWallet
CWalletDB walletdb(strWalletFile);
// First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
TxItems txOrdered;
// Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry
// would make this much faster for applications that do this a lot.
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
CWalletTx* wtx = &((*it).second);
txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0)));
}
acentries.clear();
walletdb.ListAccountCreditDebit(strAccount, acentries);
BOOST_FOREACH(CAccountingEntry& entry, acentries)
{
txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
}
return txOrdered;
}
void CWallet::MarkDirty() void CWallet::MarkDirty()
{ {
{ {
@ -629,7 +604,9 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
if (fFromLoadWallet) if (fFromLoadWallet)
{ {
mapWallet[hash] = wtxIn; mapWallet[hash] = wtxIn;
mapWallet[hash].BindWallet(this); CWalletTx& wtx = mapWallet[hash];
wtx.BindWallet(this);
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
AddToSpends(hash); AddToSpends(hash);
} }
else else
@ -644,6 +621,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
{ {
wtx.nTimeReceived = GetAdjustedTime(); wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(pwalletdb); wtx.nOrderPos = IncOrderPosNext(pwalletdb);
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
wtx.nTimeSmart = wtx.nTimeReceived; wtx.nTimeSmart = wtx.nTimeReceived;
if (!wtxIn.hashBlock.IsNull()) if (!wtxIn.hashBlock.IsNull())
@ -655,9 +633,8 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
{ {
// Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
int64_t latestTolerated = latestNow + 300; int64_t latestTolerated = latestNow + 300;
std::list<CAccountingEntry> acentries; const TxItems & txOrdered = wtxOrdered;
TxItems txOrdered = OrderedTxItems(acentries); for (TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{ {
CWalletTx *const pwtx = (*it).second.first; CWalletTx *const pwtx = (*it).second.first;
if (pwtx == &wtx) if (pwtx == &wtx)
@ -2118,6 +2095,18 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
return true; return true;
} }
bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB & pwalletdb)
{
if (!pwalletdb.WriteAccountingEntry_Backend(acentry))
return false;
laccentries.push_back(acentry);
CAccountingEntry & entry = laccentries.back();
wtxOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
return true;
}
CAmount CWallet::GetRequiredFee(unsigned int nTxBytes) CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
{ {
return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));

View file

@ -531,6 +531,11 @@ public:
} }
std::map<uint256, CWalletTx> mapWallet; std::map<uint256, CWalletTx> mapWallet;
std::list<CAccountingEntry> laccentries;
typedef std::pair<CWalletTx*, CAccountingEntry*> TxPair;
typedef std::multimap<int64_t, TxPair > TxItems;
TxItems wtxOrdered;
int64_t nOrderPosNext; int64_t nOrderPosNext;
std::map<uint256, int> mapRequestCount; std::map<uint256, int> mapRequestCount;
@ -617,16 +622,6 @@ public:
*/ */
int64_t IncOrderPosNext(CWalletDB *pwalletdb = NULL); int64_t IncOrderPosNext(CWalletDB *pwalletdb = NULL);
typedef std::pair<CWalletTx*, CAccountingEntry*> TxPair;
typedef std::multimap<int64_t, TxPair > TxItems;
/**
* Get the wallet's activity log
* @return multimap of ordered transactions and accounting entries
* @warning Returned pointers are *only* valid within the scope of passed acentries
*/
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
void MarkDirty(); void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb); bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
void SyncTransaction(const CTransaction& tx, const CBlock* pblock); void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
@ -656,6 +651,8 @@ public:
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb);
static CFeeRate minTxFee; static CFeeRate minTxFee;
/** /**
* Estimate the minimum fee considering user set parameters * Estimate the minimum fee considering user set parameters

View file

@ -191,7 +191,7 @@ bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccount
return Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry); return Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
} }
bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry) bool CWalletDB::WriteAccountingEntry_Backend(const CAccountingEntry& acentry)
{ {
return WriteAccountingEntry(++nAccountingEntryNumber, acentry); return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
} }
@ -709,6 +709,12 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
if (wss.fAnyUnordered) if (wss.fAnyUnordered)
result = ReorderTransactions(pwallet); result = ReorderTransactions(pwallet);
pwallet->laccentries.clear();
ListAccountCreditDebit("*", pwallet->laccentries);
BOOST_FOREACH(CAccountingEntry& entry, pwallet->laccentries) {
pwallet->wtxOrdered.insert(make_pair(entry.nOrderPos, CWallet::TxPair((CWalletTx*)0, &entry)));
}
return result; return result;
} }

View file

@ -110,6 +110,9 @@ public:
bool WriteMinVersion(int nVersion); bool WriteMinVersion(int nVersion);
/// This writes directly to the database, and will not update the CWallet's cached accounting entries!
/// Use wallet.AddAccountingEntry instead, to write *and* update its caches.
bool WriteAccountingEntry_Backend(const CAccountingEntry& acentry);
bool ReadAccount(const std::string& strAccount, CAccount& account); bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account); bool WriteAccount(const std::string& strAccount, const CAccount& account);
@ -118,7 +121,6 @@ public:
/// Erase destination data tuple from wallet database /// Erase destination data tuple from wallet database
bool EraseDestData(const std::string &address, const std::string &key); bool EraseDestData(const std::string &address, const std::string &key);
bool WriteAccountingEntry(const CAccountingEntry& acentry);
CAmount GetAccountCreditDebit(const std::string& strAccount); CAmount GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries); void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);