Skip to content

Commit

Permalink
Cache of runtime call results (#1680)
Browse files Browse the repository at this point in the history
* 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
xDimon authored Jul 29, 2023
1 parent 9dc1413 commit b6900de
Show file tree
Hide file tree
Showing 24 changed files with 546 additions and 162 deletions.
52 changes: 12 additions & 40 deletions core/common/CMakeLists.txt
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)
211 changes: 211 additions & 0 deletions core/common/lru_cache.hpp
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
6 changes: 3 additions & 3 deletions core/consensus/babe/impl/babe_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,14 @@ namespace kagome::consensus::babe {
switch (sync_method_) {
case SyncMethod::Auto:
if (full_sync_duration < fast_sync_duration and full_sync_available) {
SL_INFO(log_, "Sync mode auto: decided Full sync");
SL_INFO(log_, "Sync mode auto: Full sync selected");
sync_method_ = SyncMethod::Full;
} else if (fast_sync_duration < warp_sync_duration
or not allow_warp_sync_for_auto) {
SL_INFO(log_, "Sync mode auto: decided Fast sync");
SL_INFO(log_, "Sync mode auto: Fast sync selected");
sync_method_ = SyncMethod::Fast;
} else {
SL_INFO(log_, "Sync mode auto: decided Warp sync");
SL_INFO(log_, "Sync mode auto: Warp sync selected");
sync_method_ = SyncMethod::Warp;
}
break;
Expand Down
11 changes: 9 additions & 2 deletions core/primitives/babe_configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,15 @@ namespace kagome::primitives {

bool isSecondarySlotsAllowed() const {
return allowed_slots == primitives::AllowedSlots::PrimaryAndSecondaryPlain
or allowed_slots
== primitives::AllowedSlots::PrimaryAndSecondaryVRF;
or allowed_slots == primitives::AllowedSlots::PrimaryAndSecondaryVRF;
}

bool operator==(const BabeConfiguration &rhs) const {
return slot_duration == rhs.slot_duration
and epoch_length == rhs.epoch_length
and leadership_rate == rhs.leadership_rate
and authorities == rhs.authorities and randomness == rhs.randomness
and allowed_slots == rhs.allowed_slots;
}
};

Expand Down
7 changes: 5 additions & 2 deletions core/runtime/runtime_api/impl/authority_discovery_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ namespace kagome::runtime {

outcome::result<std::vector<primitives::AuthorityDiscoveryId>>
AuthorityDiscoveryApiImpl::authorities(const primitives::BlockHash &block) {
return executor_->callAt<std::vector<primitives::AuthorityDiscoveryId>>(
block, "AuthorityDiscoveryApi_authorities");
OUTCOME_TRY(ref, cache_.get_else(block, [&] {
return executor_->callAt<std::vector<primitives::AuthorityDiscoveryId>>(
block, "AuthorityDiscoveryApi_authorities");
}));
return *ref;
}
} // namespace kagome::runtime
4 changes: 4 additions & 0 deletions core/runtime/runtime_api/impl/authority_discovery_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define KAGOME_RUNTIME_RUNTIME_API_IMPL_AUTHORITY_DISCOVERY_API_HPP

#include "runtime/runtime_api/authority_discovery_api.hpp"
#include "common/lru_cache.hpp"

namespace kagome::runtime {
class Executor;
Expand All @@ -20,6 +21,9 @@ namespace kagome::runtime {

private:
std::shared_ptr<Executor> executor_;

using Auths = std::vector<primitives::AuthorityDiscoveryId>;
LruCache<primitives::BlockHash, Auths> cache_{10};
};
} // namespace kagome::runtime

Expand Down
9 changes: 6 additions & 3 deletions core/runtime/runtime_api/impl/babe_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ namespace kagome::runtime {
}

outcome::result<primitives::BabeConfiguration> BabeApiImpl::configuration(
primitives::BlockHash const &block) {
return executor_->callAt<primitives::BabeConfiguration>(
block, "BabeApi_configuration");
const primitives::BlockHash &block) {
OUTCOME_TRY(ref, cache_.get_else(block, [&] {
return executor_->callAt<primitives::BabeConfiguration>(
block, "BabeApi_configuration");
}));
return *ref;
}

} // namespace kagome::runtime
7 changes: 6 additions & 1 deletion core/runtime/runtime_api/impl/babe_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "runtime/runtime_api/babe_api.hpp"

#include "common/lru_cache.hpp"

namespace kagome::runtime {

class Executor;
Expand All @@ -17,10 +19,13 @@ namespace kagome::runtime {
explicit BabeApiImpl(std::shared_ptr<Executor> executor);

outcome::result<primitives::BabeConfiguration> configuration(
primitives::BlockHash const &block) override;
const primitives::BlockHash &block) override;

private:
std::shared_ptr<Executor> executor_;

LruCache<primitives::BlockHash, primitives::BabeConfiguration> cache_{
10};
};

} // namespace kagome::runtime
Expand Down
5 changes: 4 additions & 1 deletion core/runtime/runtime_api/impl/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ namespace kagome::runtime {

outcome::result<primitives::Version> CoreImpl::version(
const primitives::BlockHash &block) {
return executor_->callAt<primitives::Version>(block, "Core_version");
OUTCOME_TRY(ptr, version_.get_else(block, [&] {
return executor_->callAt<primitives::Version>(block, "Core_version");
}));
return *ptr;
}

outcome::result<primitives::Version> CoreImpl::version() {
Expand Down
Loading

0 comments on commit b6900de

Please sign in to comment.