userver: /data/code/userver/libraries/multi-index-lru/src/container_benchmark.cpp Source File
Loading...
Searching...
No Matches
container_benchmark.cpp
1#include <userver/engine/run_standalone.hpp>
2#include <userver/multi-index-lru/container.hpp>
3#include <userver/multi-index-lru/expirable_container.hpp>
4#include <userver/utils/rand.hpp>
5
6#include <chrono>
7#include <string>
8#include <vector>
9
10#include <benchmark/benchmark.h>
11#include <boost/multi_index/member.hpp>
12#include <boost/multi_index/ordered_index.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace benchmarks {
17
18const std::size_t kOperationsNumber = 100000;
19const int kMaxIdSize = 50000;
20
21struct IdTag {};
22struct EmailTag {};
23struct NameTag {};
24
25struct User {
26 int id;
27 std::string email;
28 std::string name;
29
30 bool operator==(const User& other) const { return id == other.id && email == other.email && name == other.name; }
31};
32
33using UserIndices = boost::multi_index::indexed_by<
34 boost::multi_index::ordered_unique<
35 boost::multi_index::tag<IdTag>,
36 boost::multi_index::member<User, int, &User::id>>,
37 boost::multi_index::ordered_unique<
38 boost::multi_index::tag<EmailTag>,
39 boost::multi_index::member<User, std::string, &User::email>>,
40 boost::multi_index::ordered_non_unique<
41 boost::multi_index::tag<NameTag>,
42 boost::multi_index::member<User, std::string, &User::name>>>;
43
44// container types
45using UserLruCache = multi_index_lru::Container<User, UserIndices>;
46using UserExpirableCache = multi_index_lru::ExpirableContainer<User, UserIndices>;
47
48// data generators
49namespace {
50
51User GenerateUser() {
52 return User{
53 utils::RandRange<int>(0, kMaxIdSize),
54 "email" + std::to_string(utils::RandRange<int>(0, kMaxIdSize)),
55 "name" + std::to_string(utils::RandRange<int>(0, kMaxIdSize))
56 };
57}
58
59int GenerateId() { return utils::RandRange<int>(0, kMaxIdSize); }
60
61std::string GenerateName() { return "name" + std::to_string(utils::RandRange<int>(0, kMaxIdSize)); }
62
63std::string GenerateEmail() { return "email" + std::to_string(utils::RandRange<int>(0, kMaxIdSize)); }
64
65template <typename Container>
66void PrepareCache(Container& cache, std::size_t size) {
67 for (std::size_t i = 0; i < size; ++i) {
68 cache.insert(GenerateUser());
69 }
70}
71
72void PrepareReadData(
73 std::vector<std::string>& names,
74 std::vector<std::string>& emails,
75 std::vector<int>& ids,
76 std::size_t count
77) {
78 names.clear();
79 emails.clear();
80 ids.clear();
81 names.reserve(count);
82 emails.reserve(count);
83 ids.reserve(count);
84
85 for (std::size_t i = 0; i < count; ++i) {
86 names.push_back(GenerateName());
87 emails.push_back(GenerateEmail());
88 ids.push_back(GenerateId());
89 }
90}
91
92void PrepareWriteData(std::vector<User>& users, std::size_t count) {
93 users.clear();
94 users.reserve(count);
95 for (std::size_t i = 0; i < count; ++i) {
96 users.push_back(GenerateUser());
97 }
98}
99
100} // namespace
101
102// template benchmark functions
103template <typename Container>
104void RunFindInsertMix(benchmark::State& state, Container& cache) {
105 const std::size_t read_ops = kOperationsNumber * 4 / 5;
106 const std::size_t write_ops = kOperationsNumber / 5;
107
108 std::vector<std::string> names, emails;
109 std::vector<int> ids;
110 std::vector<User> users;
111
112 PrepareReadData(names, emails, ids, read_ops);
113 PrepareWriteData(users, write_ops);
114
115 for ([[maybe_unused]] auto _ : state) {
116 for (std::size_t i = 0; i < read_ops; ++i) {
117 benchmark::DoNotOptimize(cache.template find<NameTag>(names[i]));
118 benchmark::DoNotOptimize(cache.template find<EmailTag>(emails[i]));
119 benchmark::DoNotOptimize(cache.template find<IdTag>(ids[i]));
120 }
121
122 for (std::size_t i = 0; i < write_ops; ++i) {
123 cache.insert(users[i]);
124 }
125 }
126}
127
128template <typename Container>
129void RunGetOperations(benchmark::State& state, Container& cache) {
130 std::vector<std::string> names, emails;
131 std::vector<int> ids;
132
133 for (auto _ : state) {
134 state.PauseTiming();
135 PrepareReadData(names, emails, ids, kOperationsNumber);
136 state.ResumeTiming();
137
138 for (std::size_t i = 0; i < kOperationsNumber; ++i) {
139 benchmark::DoNotOptimize(cache.template find<NameTag>(names[i]));
140 benchmark::DoNotOptimize(cache.template find<EmailTag>(emails[i]));
141 benchmark::DoNotOptimize(cache.template find<IdTag>(ids[i]));
142 }
143 }
144
145 state.SetItemsProcessed(state.iterations() * kOperationsNumber * 3);
146 state.SetComplexityN(state.range(0));
147}
148
149template <typename Container>
150void RunInsertOperations(benchmark::State& state, Container& cache) {
151 std::vector<User> users;
152
153 for (auto _ : state) {
154 state.PauseTiming();
155 PrepareWriteData(users, kOperationsNumber);
156 state.ResumeTiming();
157
158 for (std::size_t i = 0; i < kOperationsNumber; ++i) {
159 cache.insert(users[i]);
160 }
161 }
162
163 state.SetItemsProcessed(state.iterations() * kOperationsNumber);
164 state.SetComplexityN(state.range(0));
165}
166
167// Container's benchmarks
168static void LruFindInsertMix(benchmark::State& state) {
169 const std::size_t size = state.range(0);
170 UserLruCache cache(size);
171 PrepareCache(cache, size);
172 RunFindInsertMix(state, cache);
173}
174BENCHMARK(LruFindInsertMix)->RangeMultiplier(10)->Range(100, 1'000'000);
175
176static void LruGetOperations(benchmark::State& state) {
177 const std::size_t cache_size = state.range(0);
178 UserLruCache cache(cache_size);
179 PrepareCache(cache, cache_size);
180 RunGetOperations(state, cache);
181}
182BENCHMARK(LruGetOperations)->RangeMultiplier(10)->Range(100, 1'000'000);
183
184static void LruInsertOperations(benchmark::State& state) {
185 const std::size_t cache_size = state.range(0);
186 UserLruCache cache(cache_size);
187 PrepareCache(cache, cache_size);
188 RunInsertOperations(state, cache);
189}
190BENCHMARK(LruInsertOperations)->RangeMultiplier(10)->Range(100, 1'000'000);
191
192// ExpirableContainer's benchmarks
193static void ExpirableFindInsertMix(benchmark::State& state) {
194 engine::RunStandalone([&] {
195 const std::size_t cache_size = state.range(0);
196 UserExpirableCache cache(cache_size, std::chrono::seconds(5));
197 PrepareCache(cache, cache_size);
198 RunFindInsertMix(state, cache);
199 });
200}
201BENCHMARK(ExpirableFindInsertMix)->RangeMultiplier(10)->Range(10, 1'000'000);
202
203static void ExpirableGetOperations(benchmark::State& state) {
204 engine::RunStandalone([&] {
205 const std::size_t cache_size = state.range(0);
206 UserExpirableCache cache(cache_size, std::chrono::minutes(10));
207 PrepareCache(cache, cache_size);
208 RunGetOperations(state, cache);
209 });
210}
211BENCHMARK(ExpirableGetOperations)->RangeMultiplier(10)->Range(100, 1'000'000);
212
213static void ExpirableInsertOperations(benchmark::State& state) {
214 engine::RunStandalone([&] {
215 const std::size_t cache_size = state.range(0);
216 UserExpirableCache cache(cache_size, std::chrono::minutes(10));
217 PrepareCache(cache, cache_size);
218 RunInsertOperations(state, cache);
219 });
220}
221BENCHMARK(ExpirableInsertOperations)->RangeMultiplier(10)->Range(100, 1'000'000);
222
223} // namespace benchmarks
224
225USERVER_NAMESPACE_END