-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cache of runtime call results (#1680)
* feature: auto removal of non pruned forks * refactor: move SmallLruCache * refactor: get-or-calculate-and-put in LRU cache * refactor: add eq-operator for some types * feature: cache results of authority discovery api calls * feature: cache results of babe api calls * feature: cache results of grandpa api calls * feature: cache results of core and metadata apis calls * feature: cache results of parachain host calls * refactor: lru returns only optional sptr to value for safety * feature: method erase for force proactive removal * fix: follow actual lru interface * feature: method erase_if for force proactive removal by provided predicate * feature: prune parachain host caches by finalization * fix: optional using backward * fix: tests Signed-off-by: Dmitriy Khaustov aka xDimon <khaustov.dm@gmail.com> --------- Signed-off-by: Dmitriy Khaustov aka xDimon <khaustov.dm@gmail.com>
- Loading branch information
Showing
24 changed files
with
546 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,25 @@ | ||
# | ||
# Copyright Soramitsu Co., Ltd. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# Copyright Soramitsu Co., Ltd. All Rights Reserved. SPDX-License-Identifier: | ||
# Apache-2.0 | ||
# | ||
|
||
add_library(hexutil | ||
hexutil.hpp | ||
hexutil.cpp | ||
) | ||
target_link_libraries(hexutil | ||
Boost::boost | ||
outcome | ||
) | ||
add_library(hexutil hexutil.hpp hexutil.cpp) | ||
target_link_libraries(hexutil Boost::boost outcome) | ||
kagome_install(hexutil) | ||
|
||
add_library(blob | ||
blob.hpp | ||
blob.cpp | ||
) | ||
target_link_libraries(blob | ||
hexutil | ||
) | ||
add_library(blob blob.hpp blob.cpp) | ||
target_link_libraries(blob hexutil) | ||
kagome_install(blob) | ||
|
||
add_library(fd_limit | ||
fd_limit.hpp | ||
fd_limit.cpp | ||
) | ||
target_link_libraries(fd_limit | ||
Boost::boost | ||
logger | ||
) | ||
add_library(fd_limit fd_limit.hpp fd_limit.cpp) | ||
target_link_libraries(fd_limit Boost::boost logger) | ||
|
||
add_library(mp_utils | ||
int_serialization.cpp | ||
int_serialization.hpp | ||
) | ||
target_link_libraries(mp_utils | ||
Boost::boost | ||
) | ||
add_library(mp_utils int_serialization.cpp int_serialization.hpp) | ||
target_link_libraries(mp_utils Boost::boost) | ||
kagome_install(mp_utils) | ||
|
||
add_library(kagome_uri | ||
uri.hpp | ||
uri.cpp | ||
) | ||
add_library(kagome_uri uri.hpp uri.cpp) | ||
kagome_install(kagome_uri) | ||
|
||
add_library(spin_lock | ||
spin_lock.hpp | ||
spin_lock.cpp | ||
) | ||
add_library(spin_lock spin_lock.hpp spin_lock.cpp) | ||
kagome_install(spin_lock) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
/** | ||
* Copyright Soramitsu Co., Ltd. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#ifndef KAGOME_LRUCACHE | ||
#define KAGOME_LRUCACHE | ||
|
||
#include <algorithm> | ||
#include <cstdint> | ||
#include <functional> | ||
#include <memory> | ||
#include <optional> | ||
#include <vector> | ||
|
||
#include <boost/assert.hpp> | ||
|
||
#include "outcome/outcome.hpp" | ||
|
||
namespace kagome { | ||
|
||
namespace { | ||
|
||
template <template <bool> class Lockable, bool IsLockable> | ||
struct LockGuard { | ||
inline LockGuard(const Lockable<IsLockable> &) {} | ||
}; | ||
|
||
template <template <bool> class Lockable> | ||
struct LockGuard<Lockable, true> { | ||
LockGuard(const Lockable<true> &protected_object) | ||
: protected_object_(protected_object) { | ||
protected_object_.lock(); | ||
} | ||
~LockGuard() { | ||
protected_object_.unlock(); | ||
} | ||
|
||
private: | ||
const Lockable<true> &protected_object_; | ||
}; | ||
|
||
template <bool IsLockable> | ||
class Lockable { | ||
protected: | ||
friend struct LockGuard<Lockable, IsLockable>; | ||
inline void lock() const noexcept {} | ||
inline void unlock() const noexcept {} | ||
}; | ||
|
||
template <> | ||
class Lockable<true> { | ||
protected: | ||
friend struct LockGuard<Lockable, true>; | ||
inline void lock() const noexcept { | ||
mutex_.lock(); | ||
} | ||
inline void unlock() const noexcept { | ||
mutex_.unlock(); | ||
} | ||
mutable std::mutex mutex_; | ||
}; | ||
|
||
} // namespace | ||
|
||
/** | ||
* LRU cache designed for small amounts of data (as its get() is O(N)) | ||
*/ | ||
template <typename Key, | ||
typename Value, | ||
bool ThreadSafe = false, | ||
typename PriorityType = uint64_t> | ||
struct SmallLruCache final : public Lockable<ThreadSafe> { | ||
public: | ||
static_assert(std::is_unsigned_v<PriorityType>); | ||
|
||
using Mutex = Lockable<ThreadSafe>; | ||
|
||
struct CacheEntry { | ||
Key key; | ||
std::shared_ptr<Value> value; | ||
PriorityType latest_use_tick_; | ||
|
||
bool operator<(const CacheEntry &rhs) const { | ||
return latest_use_tick_ < rhs.latest_use_tick_; | ||
} | ||
}; | ||
|
||
SmallLruCache(size_t max_size) : kMaxSize{max_size} { | ||
BOOST_ASSERT(kMaxSize > 0); | ||
cache_.reserve(kMaxSize); | ||
} | ||
|
||
std::optional<std::shared_ptr<const Value>> get(const Key &key) { | ||
LockGuard lg(*this); | ||
if (++ticks_ == 0) { | ||
handleTicksOverflow(); | ||
} | ||
for (auto &entry : cache_) { | ||
if (entry.key == key) { | ||
entry.latest_use_tick_ = ticks_; | ||
return entry.value; | ||
} | ||
} | ||
return std::nullopt; | ||
} | ||
|
||
template <typename ValueArg> | ||
std::shared_ptr<const Value> put(const Key &key, ValueArg &&value) { | ||
static_assert(std::is_convertible_v<ValueArg, Value> | ||
|| std::is_constructible_v<ValueArg, Value>); | ||
if (cache_.size() >= kMaxSize) { | ||
auto min = std::min_element(cache_.begin(), cache_.end()); | ||
cache_.erase(min); | ||
} | ||
|
||
if (++ticks_ == 0) { | ||
handleTicksOverflow(); | ||
} | ||
|
||
if constexpr (std::is_same_v<ValueArg, Value>) { | ||
auto it = | ||
std::find_if(cache_.begin(), cache_.end(), [&](const auto &item) { | ||
return *item.value == value; | ||
}); | ||
if (it != cache_.end()) { | ||
auto &entry = cache_.emplace_back(CacheEntry{key, it->value, ticks_}); | ||
return entry.value; | ||
} | ||
auto &entry = cache_.emplace_back( | ||
CacheEntry{key, | ||
std::make_shared<Value>(std::forward<ValueArg>(value)), | ||
ticks_}); | ||
return entry.value; | ||
|
||
} else { | ||
auto value_sptr = | ||
std::make_shared<Value>(std::forward<ValueArg>(value)); | ||
auto it = | ||
std::find_if(cache_.begin(), cache_.end(), [&](const auto &item) { | ||
return *item.value == *value_sptr; | ||
}); | ||
if (it != cache_.end()) { | ||
auto &entry = cache_.emplace_back(CacheEntry{key, it->value, ticks_}); | ||
return entry.value; | ||
} | ||
auto &entry = | ||
cache_.emplace_back(CacheEntry{key, std::move(value_sptr), ticks_}); | ||
return entry.value; | ||
} | ||
} | ||
|
||
outcome::result<std::shared_ptr<const Value>> get_else( | ||
const Key &key, const std::function<outcome::result<Value>()> &func) { | ||
if (auto opt = get(key); opt.has_value()) { | ||
return opt.value(); | ||
} | ||
if (auto res = func(); res.has_value()) { | ||
return put(key, std::move(res.value())); | ||
} else { | ||
return res.as_failure(); | ||
} | ||
} | ||
|
||
void erase(const Key &key) { | ||
LockGuard lg(*this); | ||
auto it = std::find_if(cache_.begin(), | ||
cache_.end(), | ||
[&](const auto &item) { return item.key == key; }); | ||
if (it != cache_.end()) { | ||
cache_.erase(it); | ||
} | ||
} | ||
|
||
void erase_if(const std::function<bool(const Key &key, const Value &value)> | ||
&predicate) { | ||
LockGuard lg(*this); | ||
auto it = | ||
std::remove_if(cache_.begin(), cache_.end(), [&](const auto &item) { | ||
return predicate(item.key, *item.value); | ||
}); | ||
cache_.erase(it, cache_.end()); | ||
} | ||
|
||
private: | ||
void handleTicksOverflow() { | ||
// 'compress' timestamps of entries in the cache (works because we care | ||
// only about their order, not actual timestamps) | ||
std::sort(cache_.begin(), cache_.end()); | ||
for (auto &entry : cache_) { | ||
entry.latest_use_tick_ = ticks_; | ||
ticks_++; | ||
} | ||
} | ||
|
||
const size_t kMaxSize; | ||
// an abstract representation of time to implement 'recency' without | ||
// depending on real time. Incremented on each cache access | ||
PriorityType ticks_{}; | ||
std::vector<CacheEntry> cache_; | ||
}; | ||
|
||
template <typename Key, | ||
typename Value, | ||
bool ThreadSafe = true, | ||
typename PriorityType = uint64_t> | ||
using LruCache = SmallLruCache<Key, Value, ThreadSafe, PriorityType>; | ||
|
||
} // namespace kagome | ||
|
||
#endif // KAGOME_LRUCACHE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.