From e2eed966b0ecb6445027e6a023b48d702c5f4832 Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Wed, 25 May 2022 11:22:47 -0700 Subject: [PATCH] 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. --- src/ripple/app/ledger/AcceptedLedger.cpp | 4 +- src/ripple/app/ledger/AcceptedLedgerTx.cpp | 5 +- src/ripple/app/ledger/AcceptedLedgerTx.h | 3 +- src/ripple/app/main/Application.cpp | 11 +- src/ripple/app/main/Application.h | 3 - src/ripple/app/paths/PathRequest.cpp | 19 ++- src/ripple/app/rdb/backend/detail/Node.h | 4 - .../app/rdb/backend/detail/impl/Node.cpp | 21 +-- .../app/rdb/backend/impl/SQLiteDatabase.cpp | 60 +++------ src/ripple/core/Config.h | 3 +- src/ripple/core/impl/Config.cpp | 29 ++-- src/ripple/protocol/AccountID.h | 43 ++---- src/ripple/protocol/impl/AccountID.cpp | 126 +++++++++++------- src/ripple/rpc/handlers/AccountChannels.cpp | 2 +- src/ripple/rpc/handlers/AccountInfo.cpp | 2 +- src/ripple/rpc/handlers/AccountLines.cpp | 2 +- src/ripple/rpc/handlers/AccountObjects.cpp | 4 +- src/ripple/rpc/handlers/AccountOffers.cpp | 2 +- src/ripple/rpc/handlers/GatewayBalances.cpp | 2 +- src/ripple/rpc/handlers/NFTOffers.cpp | 6 +- src/ripple/rpc/handlers/NoRippleCheck.cpp | 2 +- 21 files changed, 157 insertions(+), 196 deletions(-) diff --git a/src/ripple/app/ledger/AcceptedLedger.cpp b/src/ripple/app/ledger/AcceptedLedger.cpp index 526704d1889..4f308653dcf 100644 --- a/src/ripple/app/ledger/AcceptedLedger.cpp +++ b/src/ripple/app/ledger/AcceptedLedger.cpp @@ -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( - ledger, item.first, item.second, idcache)); + ledger, item.first, item.second)); }; if (app.config().reporting()) diff --git a/src/ripple/app/ledger/AcceptedLedgerTx.cpp b/src/ripple/app/ledger/AcceptedLedgerTx.cpp index f0408b0c049..613a91e437a 100644 --- a/src/ripple/app/ledger/AcceptedLedgerTx.cpp +++ b/src/ripple/app/ledger/AcceptedLedgerTx.cpp @@ -28,8 +28,7 @@ namespace ripple { AcceptedLedgerTx::AcceptedLedgerTx( std::shared_ptr const& ledger, std::shared_ptr const& txn, - std::shared_ptr const& met, - AccountIDCache const& accountCache) + std::shared_ptr 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) diff --git a/src/ripple/app/ledger/AcceptedLedgerTx.h b/src/ripple/app/ledger/AcceptedLedgerTx.h index 7d68978571b..2995d447bba 100644 --- a/src/ripple/app/ledger/AcceptedLedgerTx.h +++ b/src/ripple/app/ledger/AcceptedLedgerTx.h @@ -46,8 +46,7 @@ class AcceptedLedgerTx : public CountedObject AcceptedLedgerTx( std::shared_ptr const& ledger, std::shared_ptr const&, - std::shared_ptr const&, - AccountIDCache const&); + std::shared_ptr const&); std::shared_ptr const& getTxn() const diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 99f3b060b9d..dce11bc38f0 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -181,7 +181,6 @@ class ApplicationImp : public Application, public BasicApp NodeStoreScheduler m_nodeStoreScheduler; std::unique_ptr m_shaMapStore; PendingSaves pendingSaves_; - AccountIDCache accountIDCache_; std::optional openLedger_; NodeCache m_tempNodeCache; @@ -336,8 +335,6 @@ class ApplicationImp : public Application, public BasicApp m_nodeStoreScheduler, logs_->journal("SHAMapStore"))) - , accountIDCache_(128000) - , m_tempNodeCache( "NodeCache", 16384, @@ -494,6 +491,8 @@ class ApplicationImp : public Application, public BasicApp config_->reporting() ? std::make_unique(*this) : nullptr) { + initAccountIdCache(config_->getValueFor(SizedItem::accountIdCacheSize)); + add(m_resourceManager.get()); // @@ -856,12 +855,6 @@ class ApplicationImp : public Application, public BasicApp return pendingSaves_; } - AccountIDCache const& - accountIDCache() const override - { - return accountIDCache_; - } - OpenLedger& openLedger() override { diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index 3b357deef3f..d8cb7d31815 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -90,7 +90,6 @@ class PathRequests; class PendingSaves; class PublicKey; class SecretKey; -class AccountIDCache; class STLedgerEntry; class TimeKeeper; class TransactionMaster; @@ -251,8 +250,6 @@ class Application : public beast::PropertyStream::Source getSHAMapStore() = 0; virtual PendingSaves& pendingSaves() = 0; - virtual AccountIDCache const& - accountIDCache() const = 0; virtual OpenLedger& openLedger() = 0; virtual OpenLedger const& diff --git a/src/ripple/app/paths/PathRequest.cpp b/src/ripple/app/paths/PathRequest.cpp index d1acb3ac1fd..02b46c81e91 100644 --- a/src/ripple/app/paths/PathRequest.cpp +++ b/src/ripple/app/paths/PathRequest.cpp @@ -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; diff --git a/src/ripple/app/rdb/backend/detail/Node.h b/src/ripple/app/rdb/backend/detail/Node.h index fa7e39c8329..0ebae76dc28 100644 --- a/src/ripple/app/rdb/backend/detail/Node.h +++ b/src/ripple/app/rdb/backend/detail/Node.h @@ -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, int> oldestAccountTxPage( soci::session& session, - AccountIDCache const& idCache, std::function 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, int> newestAccountTxPage( soci::session& session, - AccountIDCache const& idCache, std::function const& onUnsavedLedger, std::function< void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const& diff --git a/src/ripple/app/rdb/backend/detail/impl/Node.cpp b/src/ripple/app/rdb/backend/detail/impl/Node.cpp index 0c9f3b0171f..b3b354ebe72 100644 --- a/src/ripple/app/rdb/backend/detail/impl/Node.cpp +++ b/src/ripple/app/rdb/backend/detail/impl/Node.cpp @@ -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(options.offset) % beast::lexicalCastThrow(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(options.offset) % beast::lexicalCastThrow(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, int> accountTxPage( soci::session& session, - AccountIDCache const& idCache, std::function 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, int> oldestAccountTxPage( soci::session& session, - AccountIDCache const& idCache, std::function 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, int> newestAccountTxPage( soci::session& session, - AccountIDCache const& idCache, std::function 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, diff --git a/src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp b/src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp index e6ec44399a0..547ab843b36 100644 --- a/src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp +++ b/src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp @@ -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, diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index c4a5076e76f..2d440a1afd9 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -57,7 +57,8 @@ enum class SizedItem : std::size_t { lgrDBCache, openFinalLimit, burstSize, - ramSizeGB + ramSizeGB, + accountIdCacheSize, }; // This entire derived class is deprecated. diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index e53d9688392..f8d8878a771 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -108,26 +108,27 @@ namespace ripple { // clang-format off // The configurable node sizes are "tiny", "small", "medium", "large", "huge" -inline constexpr std::array>, 12> +inline constexpr std::array>, 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 diff --git a/src/ripple/protocol/AccountID.h b/src/ripple/protocol/AccountID.h index e514cfb741f..79768eefd7d 100644 --- a/src/ripple/protocol/AccountID.h +++ b/src/ripple/protocol/AccountID.h @@ -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 mutable m0_; - hash_map mutable m1_; - -public: - AccountIDCache(AccountIDCache const&) = delete; - AccountIDCache& - operator=(AccountIDCache const&) = delete; + @param count The number of entries the cache should accomodate. Zero will + disable the cache, releasing any memory associated with it. - 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 diff --git a/src/ripple/protocol/impl/AccountID.cpp b/src/ripple/protocol/impl/AccountID.cpp index 8ca8d1d153c..c615807cf84 100644 --- a/src/ripple/protocol/impl/AccountID.cpp +++ b/src/ripple/protocol/impl/AccountID.cpp @@ -17,17 +17,95 @@ */ //============================================================================== +#include +#include #include #include #include #include +#include #include +#include 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 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 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 accountIdCache; + +void +initAccountIdCache(std::size_t count) +{ + if (!accountIdCache && count != 0) + accountIdCache = std::make_unique(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 diff --git a/src/ripple/rpc/handlers/AccountChannels.cpp b/src/ripple/rpc/handlers/AccountChannels.cpp index cc79173e55f..e5059d3ffc5 100644 --- a/src/ripple/rpc/handlers/AccountChannels.cpp +++ b/src/ripple/rpc/handlers/AccountChannels.cpp @@ -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); diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index 417a3ffcd38..f5432dc65a1 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -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); } diff --git a/src/ripple/rpc/handlers/AccountLines.cpp b/src/ripple/rpc/handlers/AccountLines.cpp index 843b9ddea56..364d40673fa 100644 --- a/src/ripple/rpc/handlers/AccountLines.cpp +++ b/src/ripple/rpc/handlers/AccountLines.cpp @@ -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); diff --git a/src/ripple/rpc/handlers/AccountObjects.cpp b/src/ripple/rpc/handlers/AccountObjects.cpp index 4dcb3aba7de..6424c3afd3a 100644 --- a/src/ripple/rpc/handlers/AccountObjects.cpp +++ b/src/ripple/rpc/handlers/AccountObjects.cpp @@ -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; } diff --git a/src/ripple/rpc/handlers/AccountOffers.cpp b/src/ripple/rpc/handlers/AccountOffers.cpp index d3178756300..e957fe8a8e0 100644 --- a/src/ripple/rpc/handlers/AccountOffers.cpp +++ b/src/ripple/rpc/handlers/AccountOffers.cpp @@ -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); diff --git a/src/ripple/rpc/handlers/GatewayBalances.cpp b/src/ripple/rpc/handlers/GatewayBalances.cpp index 825a74ab843..d0770f31edf 100644 --- a/src/ripple/rpc/handlers/GatewayBalances.cpp +++ b/src/ripple/rpc/handlers/GatewayBalances.cpp @@ -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 hotWallets; diff --git a/src/ripple/rpc/handlers/NFTOffers.cpp b/src/ripple/rpc/handlers/NFTOffers.cpp index 34bbc8446b9..69a090e27ec 100644 --- a/src/ripple/rpc/handlers/NFTOffers.cpp +++ b/src/ripple/rpc/handlers/NFTOffers.cpp @@ -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); diff --git a/src/ripple/rpc/handlers/NoRippleCheck.cpp b/src/ripple/rpc/handlers/NoRippleCheck.cpp index 2a6ab7ca4ed..a2af9845fd7 100644 --- a/src/ripple/rpc/handlers/NoRippleCheck.cpp +++ b/src/ripple/rpc/handlers/NoRippleCheck.cpp @@ -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.