mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-18 17:45:48 +00:00
Improve AccountID string conversion caching:
Caching the base58check encoded version of an `AccountID` has performance advantages, because because of the computationally heavy cost associated with the conversion, which requires the application of SHA-256 twice. This commit makes the cache significantly more efficient in terms of memory used: it eliminates the map, using a vector with a size that is determined by the configured size of the node, and a hash function to directly map any given `AccountID` to a specific slot in the cache; the eviction policy is simple: in case of collision the existing entry is removed and replaced with the new data. Previously, use of the cache was optional and required additional effort by the programmer. Now the cache is automatic and does not require any additional work or information. The new cache also utilizes a 64-way spinlock, to help reduce any contention that the pressure on the cache would impose.
This commit is contained in:
@@ -31,11 +31,9 @@ AcceptedLedger::AcceptedLedger(
|
||||
transactions_.reserve(256);
|
||||
|
||||
auto insertAll = [&](auto const& txns) {
|
||||
auto const& idcache = app.accountIDCache();
|
||||
|
||||
for (auto const& item : txns)
|
||||
transactions_.emplace_back(std::make_unique<AcceptedLedgerTx>(
|
||||
ledger, item.first, item.second, idcache));
|
||||
ledger, item.first, item.second));
|
||||
};
|
||||
|
||||
if (app.config().reporting())
|
||||
|
||||
@@ -28,8 +28,7 @@ namespace ripple {
|
||||
AcceptedLedgerTx::AcceptedLedgerTx(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
std::shared_ptr<STTx const> const& txn,
|
||||
std::shared_ptr<STObject const> const& met,
|
||||
AccountIDCache const& accountCache)
|
||||
std::shared_ptr<STObject const> const& met)
|
||||
: mTxn(txn)
|
||||
, mMeta(txn->getTransactionID(), ledger->seq(), *met)
|
||||
, mAffected(mMeta.getAffectedAccounts())
|
||||
@@ -52,7 +51,7 @@ AcceptedLedgerTx::AcceptedLedgerTx(
|
||||
{
|
||||
Json::Value& affected = (mJson[jss::affected] = Json::arrayValue);
|
||||
for (auto const& account : mAffected)
|
||||
affected.append(accountCache.toBase58(account));
|
||||
affected.append(toBase58(account));
|
||||
}
|
||||
|
||||
if (mTxn->getTxnType() == ttOFFER_CREATE)
|
||||
|
||||
@@ -46,8 +46,7 @@ public:
|
||||
AcceptedLedgerTx(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
std::shared_ptr<STTx const> const&,
|
||||
std::shared_ptr<STObject const> const&,
|
||||
AccountIDCache const&);
|
||||
std::shared_ptr<STObject const> const&);
|
||||
|
||||
std::shared_ptr<STTx const> const&
|
||||
getTxn() const
|
||||
|
||||
@@ -181,7 +181,6 @@ public:
|
||||
NodeStoreScheduler m_nodeStoreScheduler;
|
||||
std::unique_ptr<SHAMapStore> m_shaMapStore;
|
||||
PendingSaves pendingSaves_;
|
||||
AccountIDCache accountIDCache_;
|
||||
std::optional<OpenLedger> openLedger_;
|
||||
|
||||
NodeCache m_tempNodeCache;
|
||||
@@ -336,8 +335,6 @@ public:
|
||||
m_nodeStoreScheduler,
|
||||
logs_->journal("SHAMapStore")))
|
||||
|
||||
, accountIDCache_(128000)
|
||||
|
||||
, m_tempNodeCache(
|
||||
"NodeCache",
|
||||
16384,
|
||||
@@ -494,6 +491,8 @@ public:
|
||||
config_->reporting() ? std::make_unique<ReportingETL>(*this)
|
||||
: nullptr)
|
||||
{
|
||||
initAccountIdCache(config_->getValueFor(SizedItem::accountIdCacheSize));
|
||||
|
||||
add(m_resourceManager.get());
|
||||
|
||||
//
|
||||
@@ -856,12 +855,6 @@ public:
|
||||
return pendingSaves_;
|
||||
}
|
||||
|
||||
AccountIDCache const&
|
||||
accountIDCache() const override
|
||||
{
|
||||
return accountIDCache_;
|
||||
}
|
||||
|
||||
OpenLedger&
|
||||
openLedger() override
|
||||
{
|
||||
|
||||
@@ -90,7 +90,6 @@ class PathRequests;
|
||||
class PendingSaves;
|
||||
class PublicKey;
|
||||
class SecretKey;
|
||||
class AccountIDCache;
|
||||
class STLedgerEntry;
|
||||
class TimeKeeper;
|
||||
class TransactionMaster;
|
||||
@@ -251,8 +250,6 @@ public:
|
||||
getSHAMapStore() = 0;
|
||||
virtual PendingSaves&
|
||||
pendingSaves() = 0;
|
||||
virtual AccountIDCache const&
|
||||
accountIDCache() const = 0;
|
||||
virtual OpenLedger&
|
||||
openLedger() = 0;
|
||||
virtual OpenLedger const&
|
||||
|
||||
@@ -552,9 +552,16 @@ PathRequest::findPaths(
|
||||
continueCallback);
|
||||
mContext[issue] = ps;
|
||||
|
||||
auto& sourceAccount = !isXRP(issue.account)
|
||||
? issue.account
|
||||
: isXRP(issue.currency) ? xrpAccount() : *raSrcAccount;
|
||||
auto const& sourceAccount = [&] {
|
||||
if (!isXRP(issue.account))
|
||||
return issue.account;
|
||||
|
||||
if (isXRP(issue.currency))
|
||||
return xrpAccount();
|
||||
|
||||
return *raSrcAccount;
|
||||
}();
|
||||
|
||||
STAmount saMaxAmount = saSendMax.value_or(
|
||||
STAmount({issue.currency, sourceAccount}, 1u, 0, true));
|
||||
|
||||
@@ -675,10 +682,8 @@ PathRequest::doUpdate(
|
||||
destCurrencies.append(to_string(c));
|
||||
}
|
||||
|
||||
newStatus[jss::source_account] =
|
||||
app_.accountIDCache().toBase58(*raSrcAccount);
|
||||
newStatus[jss::destination_account] =
|
||||
app_.accountIDCache().toBase58(*raDstAccount);
|
||||
newStatus[jss::source_account] = toBase58(*raSrcAccount);
|
||||
newStatus[jss::destination_account] = toBase58(*raDstAccount);
|
||||
newStatus[jss::destination_amount] = saDstAmount.getJson(JsonOptions::none);
|
||||
newStatus[jss::full_reply] = !fast;
|
||||
|
||||
|
||||
@@ -389,7 +389,6 @@ getNewestAccountTxsB(
|
||||
* account which match given criteria starting from given marker
|
||||
* and calls callback for each found transaction.
|
||||
* @param session Session with database.
|
||||
* @param idCache Account ID cache.
|
||||
* @param onUnsavedLedger Callback function to call on each found unsaved
|
||||
* ledger within given range.
|
||||
* @param onTransaction Callback function to call on each found transaction.
|
||||
@@ -408,7 +407,6 @@ getNewestAccountTxsB(
|
||||
std::pair<std::optional<RelationalDatabase::AccountTxMarker>, int>
|
||||
oldestAccountTxPage(
|
||||
soci::session& session,
|
||||
AccountIDCache const& idCache,
|
||||
std::function<void(std::uint32_t)> const& onUnsavedLedger,
|
||||
std::function<
|
||||
void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const&
|
||||
@@ -422,7 +420,6 @@ oldestAccountTxPage(
|
||||
* account which match given criteria starting from given marker
|
||||
* and calls callback for each found transaction.
|
||||
* @param session Session with database.
|
||||
* @param idCache Account ID cache.
|
||||
* @param onUnsavedLedger Callback function to call on each found unsaved
|
||||
* ledger within given range.
|
||||
* @param onTransaction Callback function to call on each found transaction.
|
||||
@@ -441,7 +438,6 @@ oldestAccountTxPage(
|
||||
std::pair<std::optional<RelationalDatabase::AccountTxMarker>, int>
|
||||
newestAccountTxPage(
|
||||
soci::session& session,
|
||||
AccountIDCache const& idCache,
|
||||
std::function<void(std::uint32_t)> const& onUnsavedLedger,
|
||||
std::function<
|
||||
void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const&
|
||||
|
||||
@@ -307,7 +307,7 @@ saveValidatedLedger(
|
||||
|
||||
sql += txnId;
|
||||
sql += "','";
|
||||
sql += app.accountIDCache().toBase58(account);
|
||||
sql += toBase58(account);
|
||||
sql += "',";
|
||||
sql += ledgerSeq;
|
||||
sql += ",";
|
||||
@@ -760,8 +760,7 @@ transactionsSQL(
|
||||
sql = boost::str(
|
||||
boost::format("SELECT %s FROM AccountTransactions "
|
||||
"WHERE Account = '%s' %s %s LIMIT %u, %u;") %
|
||||
selection % app.accountIDCache().toBase58(options.account) %
|
||||
maxClause % minClause %
|
||||
selection % toBase58(options.account) % maxClause % minClause %
|
||||
beast::lexicalCastThrow<std::string>(options.offset) %
|
||||
beast::lexicalCastThrow<std::string>(numberOfResults));
|
||||
else
|
||||
@@ -774,9 +773,9 @@ transactionsSQL(
|
||||
"ORDER BY AccountTransactions.LedgerSeq %s, "
|
||||
"AccountTransactions.TxnSeq %s, AccountTransactions.TransID %s "
|
||||
"LIMIT %u, %u;") %
|
||||
selection % app.accountIDCache().toBase58(options.account) %
|
||||
maxClause % minClause % (descending ? "DESC" : "ASC") %
|
||||
selection % toBase58(options.account) % maxClause % minClause %
|
||||
(descending ? "DESC" : "ASC") % (descending ? "DESC" : "ASC") %
|
||||
(descending ? "DESC" : "ASC") %
|
||||
beast::lexicalCastThrow<std::string>(options.offset) %
|
||||
beast::lexicalCastThrow<std::string>(numberOfResults));
|
||||
JLOG(j.trace()) << "txSQL query: " << sql;
|
||||
@@ -1049,7 +1048,6 @@ getNewestAccountTxsB(
|
||||
* account that matches the given criteria starting from the provided
|
||||
* marker and invokes the callback parameter for each found transaction.
|
||||
* @param session Session with the database.
|
||||
* @param idCache Account ID cache.
|
||||
* @param onUnsavedLedger Callback function to call on each found unsaved
|
||||
* ledger within the given range.
|
||||
* @param onTransaction Callback function to call on each found transaction.
|
||||
@@ -1069,7 +1067,6 @@ getNewestAccountTxsB(
|
||||
static std::pair<std::optional<RelationalDatabase::AccountTxMarker>, int>
|
||||
accountTxPage(
|
||||
soci::session& session,
|
||||
AccountIDCache const& idCache,
|
||||
std::function<void(std::uint32_t)> const& onUnsavedLedger,
|
||||
std::function<
|
||||
void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const&
|
||||
@@ -1135,8 +1132,8 @@ accountTxPage(
|
||||
ORDER BY AccountTransactions.LedgerSeq %s,
|
||||
AccountTransactions.TxnSeq %s
|
||||
LIMIT %u;)")) %
|
||||
idCache.toBase58(options.account) % options.minLedger %
|
||||
options.maxLedger % order % order % queryLimit);
|
||||
toBase58(options.account) % options.minLedger % options.maxLedger %
|
||||
order % order % queryLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1146,7 +1143,7 @@ accountTxPage(
|
||||
const std::uint32_t maxLedger =
|
||||
forward ? options.maxLedger : findLedger - 1;
|
||||
|
||||
auto b58acct = idCache.toBase58(options.account);
|
||||
auto b58acct = toBase58(options.account);
|
||||
sql = boost::str(
|
||||
boost::format((
|
||||
R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
|
||||
@@ -1250,7 +1247,6 @@ accountTxPage(
|
||||
std::pair<std::optional<RelationalDatabase::AccountTxMarker>, int>
|
||||
oldestAccountTxPage(
|
||||
soci::session& session,
|
||||
AccountIDCache const& idCache,
|
||||
std::function<void(std::uint32_t)> const& onUnsavedLedger,
|
||||
std::function<
|
||||
void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const&
|
||||
@@ -1261,7 +1257,6 @@ oldestAccountTxPage(
|
||||
{
|
||||
return accountTxPage(
|
||||
session,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
options,
|
||||
@@ -1273,7 +1268,6 @@ oldestAccountTxPage(
|
||||
std::pair<std::optional<RelationalDatabase::AccountTxMarker>, int>
|
||||
newestAccountTxPage(
|
||||
soci::session& session,
|
||||
AccountIDCache const& idCache,
|
||||
std::function<void(std::uint32_t)> const& onUnsavedLedger,
|
||||
std::function<
|
||||
void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const&
|
||||
@@ -1284,7 +1278,6 @@ newestAccountTxPage(
|
||||
{
|
||||
return accountTxPage(
|
||||
session,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
options,
|
||||
|
||||
@@ -1322,7 +1322,6 @@ SQLiteDatabaseImp::oldestAccountTxPage(AccountTxPageOptions const& options)
|
||||
return {};
|
||||
|
||||
static std::uint32_t const page_length(200);
|
||||
auto& idCache = app_.accountIDCache();
|
||||
auto onUnsavedLedger =
|
||||
std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1);
|
||||
AccountTxs ret;
|
||||
@@ -1338,15 +1337,10 @@ SQLiteDatabaseImp::oldestAccountTxPage(AccountTxPageOptions const& options)
|
||||
if (existsTransaction())
|
||||
{
|
||||
auto db = checkoutTransaction();
|
||||
auto newmarker = detail::oldestAccountTxPage(
|
||||
*db,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
options,
|
||||
0,
|
||||
page_length)
|
||||
.first;
|
||||
auto newmarker =
|
||||
detail::oldestAccountTxPage(
|
||||
*db, onUnsavedLedger, onTransaction, options, 0, page_length)
|
||||
.first;
|
||||
return {ret, newmarker};
|
||||
}
|
||||
|
||||
@@ -1363,7 +1357,6 @@ SQLiteDatabaseImp::oldestAccountTxPage(AccountTxPageOptions const& options)
|
||||
return false;
|
||||
auto [marker, total] = detail::oldestAccountTxPage(
|
||||
session,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
opt,
|
||||
@@ -1391,7 +1384,6 @@ SQLiteDatabaseImp::newestAccountTxPage(AccountTxPageOptions const& options)
|
||||
return {};
|
||||
|
||||
static std::uint32_t const page_length(200);
|
||||
auto& idCache = app_.accountIDCache();
|
||||
auto onUnsavedLedger =
|
||||
std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1);
|
||||
AccountTxs ret;
|
||||
@@ -1407,15 +1399,10 @@ SQLiteDatabaseImp::newestAccountTxPage(AccountTxPageOptions const& options)
|
||||
if (existsTransaction())
|
||||
{
|
||||
auto db = checkoutTransaction();
|
||||
auto newmarker = detail::newestAccountTxPage(
|
||||
*db,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
options,
|
||||
0,
|
||||
page_length)
|
||||
.first;
|
||||
auto newmarker =
|
||||
detail::newestAccountTxPage(
|
||||
*db, onUnsavedLedger, onTransaction, options, 0, page_length)
|
||||
.first;
|
||||
return {ret, newmarker};
|
||||
}
|
||||
|
||||
@@ -1432,7 +1419,6 @@ SQLiteDatabaseImp::newestAccountTxPage(AccountTxPageOptions const& options)
|
||||
return false;
|
||||
auto [marker, total] = detail::newestAccountTxPage(
|
||||
session,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
opt,
|
||||
@@ -1460,7 +1446,6 @@ SQLiteDatabaseImp::oldestAccountTxPageB(AccountTxPageOptions const& options)
|
||||
return {};
|
||||
|
||||
static std::uint32_t const page_length(500);
|
||||
auto& idCache = app_.accountIDCache();
|
||||
auto onUnsavedLedger =
|
||||
std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1);
|
||||
MetaTxsList ret;
|
||||
@@ -1475,15 +1460,10 @@ SQLiteDatabaseImp::oldestAccountTxPageB(AccountTxPageOptions const& options)
|
||||
if (existsTransaction())
|
||||
{
|
||||
auto db = checkoutTransaction();
|
||||
auto newmarker = detail::oldestAccountTxPage(
|
||||
*db,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
options,
|
||||
0,
|
||||
page_length)
|
||||
.first;
|
||||
auto newmarker =
|
||||
detail::oldestAccountTxPage(
|
||||
*db, onUnsavedLedger, onTransaction, options, 0, page_length)
|
||||
.first;
|
||||
return {ret, newmarker};
|
||||
}
|
||||
|
||||
@@ -1500,7 +1480,6 @@ SQLiteDatabaseImp::oldestAccountTxPageB(AccountTxPageOptions const& options)
|
||||
return false;
|
||||
auto [marker, total] = detail::oldestAccountTxPage(
|
||||
session,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
opt,
|
||||
@@ -1528,7 +1507,6 @@ SQLiteDatabaseImp::newestAccountTxPageB(AccountTxPageOptions const& options)
|
||||
return {};
|
||||
|
||||
static std::uint32_t const page_length(500);
|
||||
auto& idCache = app_.accountIDCache();
|
||||
auto onUnsavedLedger =
|
||||
std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1);
|
||||
MetaTxsList ret;
|
||||
@@ -1543,15 +1521,10 @@ SQLiteDatabaseImp::newestAccountTxPageB(AccountTxPageOptions const& options)
|
||||
if (existsTransaction())
|
||||
{
|
||||
auto db = checkoutTransaction();
|
||||
auto newmarker = detail::newestAccountTxPage(
|
||||
*db,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
options,
|
||||
0,
|
||||
page_length)
|
||||
.first;
|
||||
auto newmarker =
|
||||
detail::newestAccountTxPage(
|
||||
*db, onUnsavedLedger, onTransaction, options, 0, page_length)
|
||||
.first;
|
||||
return {ret, newmarker};
|
||||
}
|
||||
|
||||
@@ -1568,7 +1541,6 @@ SQLiteDatabaseImp::newestAccountTxPageB(AccountTxPageOptions const& options)
|
||||
return false;
|
||||
auto [marker, total] = detail::newestAccountTxPage(
|
||||
session,
|
||||
idCache,
|
||||
onUnsavedLedger,
|
||||
onTransaction,
|
||||
opt,
|
||||
|
||||
@@ -57,7 +57,8 @@ enum class SizedItem : std::size_t {
|
||||
lgrDBCache,
|
||||
openFinalLimit,
|
||||
burstSize,
|
||||
ramSizeGB
|
||||
ramSizeGB,
|
||||
accountIdCacheSize,
|
||||
};
|
||||
|
||||
// This entire derived class is deprecated.
|
||||
|
||||
@@ -108,26 +108,27 @@ namespace ripple {
|
||||
|
||||
// clang-format off
|
||||
// The configurable node sizes are "tiny", "small", "medium", "large", "huge"
|
||||
inline constexpr std::array<std::pair<SizedItem, std::array<int, 5>>, 12>
|
||||
inline constexpr std::array<std::pair<SizedItem, std::array<int, 5>>, 13>
|
||||
sizedItems
|
||||
{{
|
||||
// FIXME: We should document each of these items, explaining exactly
|
||||
// what they control and whether there exists an explicit
|
||||
// config option that can be used to override the default.
|
||||
|
||||
// tiny small medium large huge
|
||||
{SizedItem::sweepInterval, {{ 10, 30, 60, 90, 120 }}},
|
||||
{SizedItem::treeCacheSize, {{ 262144, 524288, 2097152, 4194304, 8388608 }}},
|
||||
{SizedItem::treeCacheAge, {{ 30, 60, 90, 120, 900 }}},
|
||||
{SizedItem::ledgerSize, {{ 32, 32, 64, 256, 384 }}},
|
||||
{SizedItem::ledgerAge, {{ 30, 60, 180, 300, 600 }}},
|
||||
{SizedItem::ledgerFetch, {{ 2, 3, 4, 5, 8 }}},
|
||||
{SizedItem::hashNodeDBCache, {{ 4, 12, 24, 64, 128 }}},
|
||||
{SizedItem::txnDBCache, {{ 4, 12, 24, 64, 128 }}},
|
||||
{SizedItem::lgrDBCache, {{ 4, 8, 16, 32, 128 }}},
|
||||
{SizedItem::openFinalLimit, {{ 8, 16, 32, 64, 128 }}},
|
||||
{SizedItem::burstSize, {{ 4, 8, 16, 32, 48 }}},
|
||||
{SizedItem::ramSizeGB, {{ 8, 12, 16, 24, 32 }}},
|
||||
// tiny small medium large huge
|
||||
{SizedItem::sweepInterval, {{ 10, 30, 60, 90, 120 }}},
|
||||
{SizedItem::treeCacheSize, {{ 262144, 524288, 2097152, 4194304, 8388608 }}},
|
||||
{SizedItem::treeCacheAge, {{ 30, 60, 90, 120, 900 }}},
|
||||
{SizedItem::ledgerSize, {{ 32, 32, 64, 256, 384 }}},
|
||||
{SizedItem::ledgerAge, {{ 30, 60, 180, 300, 600 }}},
|
||||
{SizedItem::ledgerFetch, {{ 2, 3, 4, 5, 8 }}},
|
||||
{SizedItem::hashNodeDBCache, {{ 4, 12, 24, 64, 128 }}},
|
||||
{SizedItem::txnDBCache, {{ 4, 12, 24, 64, 128 }}},
|
||||
{SizedItem::lgrDBCache, {{ 4, 8, 16, 32, 128 }}},
|
||||
{SizedItem::openFinalLimit, {{ 8, 16, 32, 64, 128 }}},
|
||||
{SizedItem::burstSize, {{ 4, 8, 16, 32, 48 }}},
|
||||
{SizedItem::ramSizeGB, {{ 8, 12, 16, 24, 32 }}},
|
||||
{SizedItem::accountIdCacheSize, {{ 20047, 50053, 77081, 150061, 300007 }}}
|
||||
}};
|
||||
|
||||
// Ensure that the order of entries in the table corresponds to the
|
||||
|
||||
@@ -106,41 +106,20 @@ operator<<(std::ostream& os, AccountID const& x)
|
||||
return os;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/** Initialize the global cache used to map AccountID to base58 conversions.
|
||||
|
||||
/** Caches the base58 representations of AccountIDs
|
||||
The cache is optional and need not be initialized. But because conversion
|
||||
is expensive (it requires a SHA-256 operation) in most cases the overhead
|
||||
of the cache is worth the benefit.
|
||||
|
||||
This operation occurs with sufficient frequency to
|
||||
justify having a cache. In the future, rippled should
|
||||
require clients to receive "binary" results, where
|
||||
AccountIDs are hex-encoded.
|
||||
*/
|
||||
class AccountIDCache
|
||||
{
|
||||
private:
|
||||
std::mutex mutable mutex_;
|
||||
std::size_t capacity_;
|
||||
hash_map<AccountID, std::string> mutable m0_;
|
||||
hash_map<AccountID, std::string> mutable m1_;
|
||||
@param count The number of entries the cache should accomodate. Zero will
|
||||
disable the cache, releasing any memory associated with it.
|
||||
|
||||
public:
|
||||
AccountIDCache(AccountIDCache const&) = delete;
|
||||
AccountIDCache&
|
||||
operator=(AccountIDCache const&) = delete;
|
||||
|
||||
explicit AccountIDCache(std::size_t capacity);
|
||||
|
||||
/** Return ripple::toBase58 for the AccountID
|
||||
|
||||
Thread Safety:
|
||||
Safe to call from any thread concurrently
|
||||
|
||||
@note This function intentionally returns a
|
||||
copy for correctness.
|
||||
*/
|
||||
std::string
|
||||
toBase58(AccountID const&) const;
|
||||
};
|
||||
@note The function will only initialize the cache the first time it is
|
||||
invoked. Subsequent invocations do nothing.
|
||||
*/
|
||||
void
|
||||
initAccountIdCache(std::size_t count);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -17,17 +17,95 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/hardened_hash.h>
|
||||
#include <ripple/basics/spinlock.h>
|
||||
#include <ripple/protocol/AccountID.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/protocol/tokens.h>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/** Caches the base58 representations of AccountIDs */
|
||||
class AccountIdCache
|
||||
{
|
||||
private:
|
||||
struct CachedAccountID
|
||||
{
|
||||
AccountID id;
|
||||
char encoding[40] = {0};
|
||||
};
|
||||
|
||||
// The actual cache
|
||||
std::vector<CachedAccountID> cache_;
|
||||
|
||||
// We use a hash function designed to resist algorithmic complexity attacks
|
||||
hardened_hash<> hasher_;
|
||||
|
||||
// 64 spinlocks, packed into a single 64-bit value
|
||||
std::atomic<std::uint64_t> locks_ = 0;
|
||||
|
||||
public:
|
||||
AccountIdCache(std::size_t count) : cache_(count)
|
||||
{
|
||||
// This is non-binding, but we try to avoid wasting memory that
|
||||
// is caused by overallocation.
|
||||
cache_.shrink_to_fit();
|
||||
}
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& id)
|
||||
{
|
||||
auto const index = hasher_(id) % cache_.size();
|
||||
|
||||
packed_spinlock sl(locks_, index % 64);
|
||||
|
||||
{
|
||||
std::lock_guard lock(sl);
|
||||
|
||||
// The check against the first character of the encoding ensures
|
||||
// that we don't mishandle the case of the all-zero account:
|
||||
if (cache_[index].encoding[0] != 0 && cache_[index].id == id)
|
||||
return cache_[index].encoding;
|
||||
}
|
||||
|
||||
auto ret =
|
||||
encodeBase58Token(TokenType::AccountID, id.data(), id.size());
|
||||
|
||||
assert(ret.size() <= 38);
|
||||
|
||||
{
|
||||
std::lock_guard lock(sl);
|
||||
cache_[index].id = id;
|
||||
std::strcpy(cache_[index].encoding, ret.c_str());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
static std::unique_ptr<detail::AccountIdCache> accountIdCache;
|
||||
|
||||
void
|
||||
initAccountIdCache(std::size_t count)
|
||||
{
|
||||
if (!accountIdCache && count != 0)
|
||||
accountIdCache = std::make_unique<detail::AccountIdCache>(count);
|
||||
}
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& v)
|
||||
{
|
||||
if (accountIdCache)
|
||||
return accountIdCache->toBase58(v);
|
||||
|
||||
return encodeBase58Token(TokenType::AccountID, v.data(), v.size());
|
||||
}
|
||||
|
||||
@@ -112,52 +190,4 @@ to_issuer(AccountID& issuer, std::string const& s)
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* VFALCO NOTE
|
||||
An alternate implementation could use a pair of insert-only
|
||||
hash maps that each use a single large memory allocation
|
||||
to store a fixed size hash table and all of the AccountID/string
|
||||
pairs laid out in memory (wouldn't use std::string here just a
|
||||
length prefixed or zero terminated array). Possibly using
|
||||
boost::intrusive as the basis for the unordered container.
|
||||
This would cut down to one allocate/free cycle per swap of
|
||||
the map.
|
||||
*/
|
||||
|
||||
AccountIDCache::AccountIDCache(std::size_t capacity) : capacity_(capacity)
|
||||
{
|
||||
m1_.reserve(capacity_);
|
||||
}
|
||||
|
||||
std::string
|
||||
AccountIDCache::toBase58(AccountID const& id) const
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
auto iter = m1_.find(id);
|
||||
if (iter != m1_.end())
|
||||
return iter->second;
|
||||
iter = m0_.find(id);
|
||||
std::string result;
|
||||
if (iter != m0_.end())
|
||||
{
|
||||
result = iter->second;
|
||||
// Can use insert-only hash maps if
|
||||
// we didn't erase from here.
|
||||
m0_.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ripple::toBase58(id);
|
||||
}
|
||||
if (m1_.size() >= capacity_)
|
||||
{
|
||||
m0_ = std::move(m1_);
|
||||
m1_.clear();
|
||||
m1_.reserve(capacity_);
|
||||
}
|
||||
m1_.emplace(id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -202,7 +202,7 @@ doAccountChannels(RPC::JsonContext& context)
|
||||
to_string(*marker) + "," + std::to_string(nextHint);
|
||||
}
|
||||
|
||||
result[jss::account] = context.app.accountIDCache().toBase58(accountID);
|
||||
result[jss::account] = toBase58(accountID);
|
||||
|
||||
for (auto const& item : visitData.items)
|
||||
addChannel(jsonChannels, *item);
|
||||
|
||||
@@ -215,7 +215,7 @@ doAccountInfo(RPC::JsonContext& context)
|
||||
}
|
||||
else
|
||||
{
|
||||
result[jss::account] = context.app.accountIDCache().toBase58(accountID);
|
||||
result[jss::account] = toBase58(accountID);
|
||||
RPC::inject_error(rpcACT_NOT_FOUND, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ doAccountLines(RPC::JsonContext& context)
|
||||
to_string(*marker) + "," + std::to_string(nextHint);
|
||||
}
|
||||
|
||||
result[jss::account] = context.app.accountIDCache().toBase58(accountID);
|
||||
result[jss::account] = toBase58(accountID);
|
||||
|
||||
for (auto const& item : visitData.items)
|
||||
addLine(jsonLines, item);
|
||||
|
||||
@@ -160,7 +160,7 @@ doAccountNFTs(RPC::JsonContext& context)
|
||||
cp = nullptr;
|
||||
}
|
||||
|
||||
result[jss::account] = context.app.accountIDCache().toBase58(accountID);
|
||||
result[jss::account] = toBase58(accountID);
|
||||
context.loadType = Resource::feeMediumBurdenRPC;
|
||||
return result;
|
||||
}
|
||||
@@ -275,7 +275,7 @@ doAccountObjects(RPC::JsonContext& context)
|
||||
result[jss::account_objects] = Json::arrayValue;
|
||||
}
|
||||
|
||||
result[jss::account] = context.app.accountIDCache().toBase58(accountID);
|
||||
result[jss::account] = toBase58(accountID);
|
||||
context.loadType = Resource::feeMediumBurdenRPC;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ doAccountOffers(RPC::JsonContext& context)
|
||||
}
|
||||
|
||||
// Get info on account.
|
||||
result[jss::account] = context.app.accountIDCache().toBase58(accountID);
|
||||
result[jss::account] = toBase58(accountID);
|
||||
|
||||
if (!ledger->exists(keylet::account(accountID)))
|
||||
return rpcError(rpcACT_NOT_FOUND);
|
||||
|
||||
@@ -80,7 +80,7 @@ doGatewayBalances(RPC::JsonContext& context)
|
||||
|
||||
context.loadType = Resource::feeHighBurdenRPC;
|
||||
|
||||
result[jss::account] = context.app.accountIDCache().toBase58(accountID);
|
||||
result[jss::account] = toBase58(accountID);
|
||||
|
||||
// Parse the specified hotwallet(s), if any
|
||||
std::set<AccountID> hotWallets;
|
||||
|
||||
@@ -41,12 +41,10 @@ appendNftOfferJson(
|
||||
|
||||
obj[jss::nft_offer_index] = to_string(offer->key());
|
||||
obj[jss::flags] = (*offer)[sfFlags];
|
||||
obj[jss::owner] =
|
||||
app.accountIDCache().toBase58(offer->getAccountID(sfOwner));
|
||||
obj[jss::owner] = toBase58(offer->getAccountID(sfOwner));
|
||||
|
||||
if (offer->isFieldPresent(sfDestination))
|
||||
obj[jss::destination] =
|
||||
app.accountIDCache().toBase58(offer->getAccountID(sfDestination));
|
||||
obj[jss::destination] = toBase58(offer->getAccountID(sfDestination));
|
||||
|
||||
if (offer->isFieldPresent(sfExpiration))
|
||||
obj[jss::expiration] = offer->getFieldU32(sfExpiration);
|
||||
|
||||
@@ -40,7 +40,7 @@ fillTransaction(
|
||||
ReadView const& ledger)
|
||||
{
|
||||
txArray["Sequence"] = Json::UInt(sequence++);
|
||||
txArray["Account"] = context.app.accountIDCache().toBase58(accountID);
|
||||
txArray["Account"] = toBase58(accountID);
|
||||
auto& fees = ledger.fees();
|
||||
// Convert the reference transaction cost in fee units to drops
|
||||
// scaled to represent the current fee load.
|
||||
|
||||
Reference in New Issue
Block a user