Skip to content
This repository was archived by the owner on Mar 22, 2023. It is now read-only.

Commit a2cb574

Browse files
Merge pull request #1038 from JanDorniak99/add_radix_benchmark
add benchmark which compares radix to std::map
2 parents 485353b + 1f0dbdd commit a2cb574

2 files changed

Lines changed: 289 additions & 1 deletion

File tree

benchmarks/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SPDX-License-Identifier: BSD-3-Clause
2-
# Copyright 2020, Intel Corporation
2+
# Copyright 2020-2021, Intel Corporation
33

44
if(MSVC_VERSION)
55
add_flag(-W2)
@@ -40,6 +40,9 @@ add_check_whitespace(benchmarks-concurrent_hash_map ${CMAKE_CURRENT_SOURCE_DIR}/
4040
add_cppstyle(benchmarks-self-relative-pointer ${CMAKE_CURRENT_SOURCE_DIR}/self_relative_pointer/*.*pp)
4141
add_check_whitespace(benchmarks-self-relative-pointer ${CMAKE_CURRENT_SOURCE_DIR}/self_relative_pointer/*.*pp)
4242

43+
add_cppstyle(benchmarks-radix_tree ${CMAKE_CURRENT_SOURCE_DIR}/radix/*.*pp)
44+
add_check_whitespace(benchmarks-radix_tree ${CMAKE_CURRENT_SOURCE_DIR}/radix/*.*pp)
45+
4346
if (TEST_CONCURRENT_HASHMAP)
4447
add_benchmark(concurrent_hash_map_insert_open concurrent_hash_map/insert_open.cpp)
4548
endif()
@@ -48,3 +51,7 @@ if (TEST_SELF_RELATIVE_POINTER)
4851
add_benchmark(self_relative_pointer_get self_relative_pointer/get.cpp)
4952
add_benchmark(self_relative_pointer_assignment self_relative_pointer/assignment.cpp)
5053
endif()
54+
55+
if (TEST_RADIX_TREE)
56+
add_benchmark(radix_tree radix/radix_tree.cpp)
57+
endif()

benchmarks/radix/radix_tree.cpp

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
/* Copyright 2021, Intel Corporation */
3+
4+
/**
5+
* radix_tree.cpp -- this simple benchmark is used to compare times of basic
6+
* operations in radix_tree and std::map
7+
*/
8+
9+
#include <map>
10+
#include <vector>
11+
12+
#include <libpmemobj++/experimental/inline_string.hpp>
13+
#include <libpmemobj++/experimental/radix_tree.hpp>
14+
#include <libpmemobj++/make_persistent.hpp>
15+
#include <libpmemobj++/p.hpp>
16+
#include <libpmemobj++/pool.hpp>
17+
18+
#include "../measure.hpp"
19+
20+
struct data {
21+
size_t index;
22+
size_t data_1, data_2;
23+
};
24+
25+
struct {
26+
size_t count = 10000;
27+
size_t sample_size = 100;
28+
size_t batch_size = 1000;
29+
} params;
30+
31+
using value_type = struct data;
32+
33+
using kv_type = pmem::obj::experimental::radix_tree<size_t, value_type>;
34+
35+
struct root {
36+
pmem::obj::persistent_ptr<kv_type> kv;
37+
};
38+
39+
static std::map<size_t, value_type> mymap;
40+
41+
void
42+
show_usage(char *argv[])
43+
{
44+
std::cerr << "usage: " << argv[0]
45+
<< " file-name [count] [batch_size] [sample_size]"
46+
<< std::endl;
47+
}
48+
49+
void
50+
print_time_per_element(std::string msg,
51+
std::chrono::nanoseconds::rep total_time,
52+
size_t n_elements)
53+
{
54+
std::cout << msg << static_cast<size_t>(total_time) / n_elements << "ns"
55+
<< std::endl;
56+
}
57+
58+
/* prepare odd keys to check finding non-existing keys (in the containers will
59+
* be only even keys) */
60+
std::vector<size_t>
61+
gen_nonexisting_keys(size_t count, size_t sample_size)
62+
{
63+
size_t key;
64+
size_t key1;
65+
66+
std::vector<size_t> ret;
67+
ret.reserve(count / sample_size);
68+
for (size_t i = 0; i < (count / sample_size); ++i) {
69+
key = static_cast<size_t>(rand());
70+
key <<= 32;
71+
key1 = static_cast<size_t>(rand());
72+
key |= (key1 | 0x1);
73+
ret.emplace_back(key);
74+
}
75+
return ret;
76+
}
77+
78+
std::vector<size_t>
79+
gen_keys(size_t count)
80+
{
81+
size_t key;
82+
size_t key1;
83+
84+
std::vector<size_t> ret;
85+
ret.reserve(count);
86+
for (size_t i = 0; i < count; ++i) {
87+
/* only even keys will be insterted */
88+
key = static_cast<size_t>(rand());
89+
key <<= 32;
90+
key1 = static_cast<size_t>(rand());
91+
key |= (key1 & 0x0);
92+
ret.emplace_back(key);
93+
}
94+
return ret;
95+
}
96+
97+
/* insert_f must insert batch_size elements */
98+
template <typename F>
99+
void
100+
insert_elements_kv(F &&insert_f, const std::string &container)
101+
{
102+
std::chrono::nanoseconds::rep insert_time = 0;
103+
104+
std::cout << "Inserting " << params.count << " elements..."
105+
<< std::endl;
106+
107+
for (size_t i = 0; i < params.count; i += params.batch_size) {
108+
insert_time +=
109+
measure<std::chrono::nanoseconds>([&] { insert_f(i); });
110+
}
111+
print_time_per_element("Average insert time: (" + container + "): ",
112+
insert_time, params.count);
113+
}
114+
115+
void
116+
lookup_elements_kv(pmem::obj::pool<root> pop, std::vector<size_t> &keys)
117+
{
118+
auto r = pop.root();
119+
120+
std::cout << "Looking up " << params.count / params.sample_size
121+
<< " elements..." << std::endl;
122+
123+
kv_type::iterator res_radix;
124+
std::map<size_t, value_type>::iterator res_map;
125+
126+
auto radix_time = measure<std::chrono::nanoseconds>([&] {
127+
for (size_t i = 0; i < params.count; i += params.sample_size)
128+
res_radix = r->kv->find(keys[i]);
129+
});
130+
print_time_per_element("Average access time (persistent radix tree): ",
131+
radix_time, params.count / params.sample_size);
132+
133+
auto std_map_time = measure<std::chrono::nanoseconds>([&] {
134+
for (size_t i = 0; i < params.count; i += params.sample_size)
135+
res_map = mymap.find(keys[i]);
136+
});
137+
print_time_per_element("Average access time (map): ", std_map_time,
138+
params.count / params.sample_size);
139+
}
140+
141+
void
142+
lookup_ne_elements_kv(pmem::obj::pool<root> pop,
143+
std::vector<size_t> &non_existing_keys)
144+
{
145+
auto r = pop.root();
146+
147+
std::cout << "[Key not present] Looking up " << non_existing_keys.size()
148+
<< " elements..." << std::endl;
149+
150+
kv_type::iterator res_radix;
151+
std::map<size_t, value_type>::iterator res_map;
152+
153+
auto radix_time = measure<std::chrono::nanoseconds>([&] {
154+
for (auto &key : non_existing_keys)
155+
res_radix = r->kv->find(key);
156+
});
157+
print_time_per_element(
158+
"[Key not present] Average access time (persistent radix tree): ",
159+
radix_time, non_existing_keys.size());
160+
161+
auto std_map_time = measure<std::chrono::nanoseconds>([&] {
162+
for (auto &key : non_existing_keys) {
163+
res_map = mymap.find(key);
164+
}
165+
});
166+
print_time_per_element("[Key not present] Average access time (map): ",
167+
std_map_time, non_existing_keys.size());
168+
}
169+
170+
void
171+
remove_all_elements_kv(pmem::obj::pool<root> pop, std::vector<size_t> &keys)
172+
{
173+
auto r = pop.root();
174+
175+
std::cout << "Removing " << keys.size() << " elements..." << std::endl;
176+
177+
auto radix_time = measure<std::chrono::nanoseconds>([&] {
178+
for (auto it = r->kv->begin(); it != r->kv->end();)
179+
it = r->kv->erase(it);
180+
});
181+
print_time_per_element("Average remove time (persistent radix tree): ",
182+
radix_time, keys.size());
183+
184+
auto std_map_time = measure<std::chrono::nanoseconds>([&] {
185+
for (auto it = mymap.begin(); it != mymap.end();) {
186+
auto tmp_it = it++;
187+
mymap.erase(tmp_it);
188+
}
189+
});
190+
print_time_per_element("Average remove time (map): ", std_map_time,
191+
keys.size());
192+
}
193+
194+
int
195+
main(int argc, char *argv[])
196+
{
197+
if (argc < 2) {
198+
show_usage(argv);
199+
return 1;
200+
}
201+
202+
const char *path = argv[1];
203+
if (argc > 2)
204+
params.count = std::stoul(argv[2]);
205+
if (argc > 3)
206+
params.batch_size = std::stoul(argv[3]);
207+
if (argc > 4)
208+
params.sample_size = std::stoul(argv[4]);
209+
210+
std::cout << "Radix benchmark, count: " << params.count
211+
<< ", batch_size: " << params.batch_size
212+
<< ", sample_size: " << params.sample_size << std::endl;
213+
214+
srand(time(0));
215+
216+
pmem::obj::pool<root> pop;
217+
218+
try {
219+
pop = pmem::obj::pool<root>::open(path, "radix");
220+
auto r = pop.root();
221+
222+
if (r->kv == nullptr) {
223+
pmem::obj::transaction::run(pop, [&] {
224+
r->kv = pmem::obj::make_persistent<kv_type>();
225+
});
226+
}
227+
} catch (pmem::pool_error &e) {
228+
std::cerr
229+
<< e.what() << std::endl
230+
<< "To create pool run: pmempool create obj --layout=radix -s 1G path_to_pool"
231+
<< std::endl;
232+
} catch (std::exception &e) {
233+
std::cerr << e.what() << std::endl;
234+
}
235+
236+
auto non_existing_keys =
237+
gen_nonexisting_keys(params.count, params.sample_size);
238+
auto keys_to_insert = gen_keys(params.count);
239+
try {
240+
insert_elements_kv(
241+
[&](size_t start) {
242+
auto r = pop.root();
243+
pmem::obj::transaction::run(pop, [&] {
244+
for (size_t i = start;
245+
i < start + params.batch_size;
246+
++i) {
247+
value_type value{i, i + 1,
248+
i + 2};
249+
250+
r->kv->try_emplace(
251+
keys_to_insert[i],
252+
value);
253+
}
254+
});
255+
},
256+
"persistent radix tree");
257+
258+
insert_elements_kv(
259+
[&](size_t start) {
260+
for (size_t i = start;
261+
i < start + params.batch_size; ++i) {
262+
value_type map_value{i, i + 1, i + 2};
263+
264+
mymap.emplace(keys_to_insert[i],
265+
map_value);
266+
}
267+
},
268+
"map");
269+
270+
lookup_elements_kv(pop, keys_to_insert);
271+
lookup_ne_elements_kv(pop, non_existing_keys);
272+
remove_all_elements_kv(pop, keys_to_insert);
273+
274+
pop.close();
275+
} catch (std::exception &e) {
276+
std::cerr << e.what() << std::endl;
277+
} catch (...) {
278+
std::cerr << "Unexpected exception" << std::endl;
279+
}
280+
return 0;
281+
}

0 commit comments

Comments
 (0)