Skip to content

Commit 888559f

Browse files
hzhdlrpapolukhin
authored andcommitted
feat multi-index-lru: extract and reusе nodes, avoid friend relations
Extract and reuse nodes for better efficiency Tests: протестировано CI --- Pull Request resolved: #1155 Co-authored-by: antoshkka <antoshkka@userver.tech> Co-authored-by: antoshkka <antoshkka@userver.tech> Co-authored-by: antoshkka <antoshkka@userver.tech> Co-authored-by: antoshkka <antoshkka@userver.tech> Co-authored-by: antoshkka <antoshkka@userver.tech> Co-authored-by: antoshkka <antoshkka@userver.tech> commit_hash:97cb1958b739b5f3027c3de76d7e5416027e1898
1 parent fc06428 commit 888559f

File tree

3 files changed

+103
-17
lines changed

3 files changed

+103
-17
lines changed

libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
/// @file userver/multi-index-lru/container.hpp
44
/// @brief @copybrief multi_index_lru::Container
55

6+
#include <vector>
7+
68
#include "impl/mpl_helpers.hpp"
79

810
USERVER_NAMESPACE_BEGIN
@@ -25,12 +27,23 @@ class Container {
2527
template <typename... Args>
2628
auto emplace(Args&&... args) {
2729
auto& seq_index = get_sequenced();
28-
auto result = seq_index.emplace_front(std::forward<Args>(args)...);
2930

31+
std::pair<decltype(seq_index.begin()), bool> result;
32+
33+
if (free_nodes_.size() > 0) {
34+
auto node = std::move(free_nodes_.back());
35+
free_nodes_.pop_back();
36+
37+
node.value() = Value(std::forward<Args>(args)...);
38+
auto ret = seq_index.insert(seq_index.begin(), std::move(node));
39+
result = {ret.position, ret.inserted};
40+
} else {
41+
result = seq_index.emplace_front(std::forward<Args>(args)...);
42+
}
3043
if (!result.second) {
3144
seq_index.relocate(seq_index.begin(), result.first);
3245
} else if (seq_index.size() > max_size_) {
33-
seq_index.pop_back();
46+
free_nodes_.emplace_back(seq_index.extract(std::prev(seq_index.end())));
3447
}
3548
return result;
3649
}
@@ -89,39 +102,76 @@ class Container {
89102
return this->template find_no_update<Tag, Key>(key) != end<Tag>();
90103
}
91104

105+
/// Removes the @b it from container, leaving the node in an internal pool. The key and value are not destroyed
106+
/// and are reused on next insertion.
107+
template <typename IteratorType>
108+
auto erase(IteratorType it) {
109+
auto it_0 = project_to_sequenced(it);
110+
auto& seq_index = get_sequenced();
111+
if (it_0 == seq_index.end()) {
112+
return it;
113+
}
114+
115+
++it;
116+
free_nodes_.emplace_back(seq_index.extract(it_0));
117+
return it;
118+
}
119+
120+
/// Removes the @b key from container, leaving the node in an internal pool. The key and value are not destroyed
121+
/// and are reused on next insertion.
92122
template <typename Tag, typename Key>
93123
bool erase(const Key& key) {
94-
return get_index<Tag>().erase(key) > 0;
124+
auto it = find_no_update<Tag, Key>(key);
125+
return erase(it) != end<Tag>();
95126
}
96127

97128
std::size_t size() const noexcept { return container_.size(); }
129+
98130
bool empty() const noexcept { return container_.empty(); }
131+
99132
std::size_t capacity() const noexcept { return max_size_; }
100133

101134
void set_capacity(std::size_t new_capacity) {
102135
max_size_ = new_capacity;
136+
if (container_.size() <= max_size_) {
137+
return;
138+
}
139+
140+
shrink_to_fit();
103141
auto& seq_index = get_sequenced();
104142
while (container_.size() > max_size_) {
105-
seq_index.pop_back();
143+
// Not putting the node to `free_nodes_`, because the container is already full
144+
seq_index.extract(std::prev(seq_index.end()));
106145
}
107146
}
108147

109-
void clear() { container_.clear(); }
148+
/// Clears the internal nodes pool
149+
void shrink_to_fit() { free_nodes_.clear(); }
150+
151+
/// Removes all elements from the container, leaving the node in an internal pool. The keys and values are
152+
/// not destroyed and are reused on next insertion.
153+
void clear() {
154+
auto& seq_index = get_sequenced();
155+
free_nodes_.reserve(free_nodes_.size() + container_.size());
156+
while (!container_.empty()) {
157+
free_nodes_.emplace_back(seq_index.extract(std::prev(seq_index.end())));
158+
}
159+
}
110160

111161
template <typename Tag>
112162
auto end() const {
113163
return get_index<Tag>().end();
114164
}
115165

116-
private:
117-
template <typename V, typename I, typename A>
118-
friend class ExpirableContainer;
166+
auto find_last_accessed_no_update() const { return std::prev(get_sequenced().end()); }
119167

168+
private:
120169
using ExtendedIndexSpecifierList = impl::add_index_t<boost::multi_index::sequenced<>, IndexSpecifierList>;
121170
using BoostContainer = boost::multi_index::multi_index_container<Value, ExtendedIndexSpecifierList, Allocator>;
122171

123172
BoostContainer container_;
124173
std::size_t max_size_;
174+
std::vector<typename BoostContainer::node_type> free_nodes_;
125175

126176
auto& get_sequenced() noexcept { return container_.template get<0>(); }
127177

libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class ExpirableContainer {
2222
: container_(max_size),
2323
ttl_(ttl)
2424
{
25-
UASSERT_MSG(ttl.count() > 0, "ttl must be positive");
25+
UINVARIANT(ttl.count() > 0, "ttl must be positive");
26+
UINVARIANT(max_size > 0, "capacity must be positive");
2627
}
2728

2829
template <typename... Args>
@@ -47,7 +48,7 @@ class ExpirableContainer {
4748

4849
if (it != container_.template end<Tag>()) {
4950
if (now > it->last_accessed + ttl_) {
50-
container_.template get_index<Tag>().erase(it);
51+
container_.erase(it);
5152
return end<Tag>();
5253
} else {
5354
it->last_accessed = now;
@@ -66,23 +67,22 @@ class ExpirableContainer {
6667
template <typename Tag, typename Key>
6768
auto equal_range(const Key& key) {
6869
const auto now = std::chrono::steady_clock::now();
69-
auto& index = container_.template get_index<Tag>();
7070
auto range = container_.template equal_range<Tag, Key>(key);
7171

7272
auto it = range.first;
7373
bool changed = false;
7474

7575
while (it != range.second) {
7676
if (now > it->last_accessed + ttl_) {
77-
it = index.erase(it);
77+
it = container_.erase(it);
7878
changed = true;
7979
} else {
8080
it->last_accessed = now;
8181
++it;
8282
}
8383
}
8484
if (changed) {
85-
range = index.equal_range(key);
85+
range = container_.template equal_range_no_update<Tag, Key>(key);
8686
}
8787
return std::pair{impl::IteratorToValue{range.first}, impl::IteratorToValue{range.second}};
8888
}
@@ -103,17 +103,33 @@ class ExpirableContainer {
103103
return this->template find_no_update<Tag, Key>(key) != this->template end<Tag>();
104104
}
105105

106+
/// Removes the @b it from container, leaving the node in an internal pool. The key and value are not destroyed
107+
/// and are reused on next insertion.
108+
template <typename IteratorType>
109+
bool erase(IteratorType it) {
110+
return container_.template erase(it);
111+
}
112+
113+
/// Removes the @b key from container, leaving the node in an internal pool. The key and value are not destroyed
114+
/// and are reused on next insertion.
106115
template <typename Tag, typename Key>
107116
bool erase(const Key& key) {
108117
return container_.template erase<Tag, Key>(key);
109118
}
110119

111120
std::size_t size() const noexcept { return container_.size(); }
121+
112122
bool empty() const noexcept { return container_.empty(); }
123+
113124
std::size_t capacity() const noexcept { return container_.capacity(); }
114125

115126
void set_capacity(std::size_t new_capacity) { container_.set_capacity(new_capacity); }
116127

128+
/// Clears the internal nodes pool
129+
void shrink_to_fit() { container_.shrink_to_fit(); }
130+
131+
/// Removes all elements from the container, leaving the node in an internal pool. The keys and values are
132+
/// not destroyed and are reused on next insertion.
117133
void clear() { container_.clear(); }
118134

119135
template <typename Tag>
@@ -123,12 +139,11 @@ class ExpirableContainer {
123139

124140
void cleanup_expired() {
125141
const auto now = std::chrono::steady_clock::now();
126-
auto& seq_index = container_.get_sequenced();
127142

128-
while (!seq_index.empty()) {
129-
const auto it = seq_index.rbegin();
143+
while (!container_.empty()) {
144+
const auto it = container_.find_last_accessed_no_update();
130145
if (now > it->last_accessed + ttl_) {
131-
seq_index.pop_back();
146+
container_.erase(it);
132147
} else {
133148
break;
134149
}

libraries/multi-index-lru/src/expirable_container_test.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,27 @@ UTEST_F(ExpirableUsersTest, ThreadSafetyBasic) {
305305
EXPECT_LE(cache.size(), 100);
306306
}
307307

308+
#ifdef NDEBUG
309+
310+
UTEST_F(ExpirableUsersTest, ZeroTTL) {
311+
using namespace std::chrono_literals;
312+
313+
EXPECT_THROW({ UserCacheExpirable cache(10, 0ms); }, utils::InvariantError);
314+
}
315+
316+
UTEST_F(ExpirableUsersTest, ZeroCapacity) {
317+
using namespace std::chrono_literals;
318+
319+
EXPECT_THROW({ UserCacheExpirable cache(0, 10s); }, utils::InvariantError);
320+
}
321+
322+
UTEST_F(ExpirableUsersTest, NegativeTTL) {
323+
using namespace std::chrono_literals;
324+
325+
EXPECT_THROW({ UserCacheExpirable cache(10, -1ms); }, utils::InvariantError);
326+
}
327+
#endif
328+
308329
} // namespace
309330

310331
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)