mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-22 17:40:34 +00:00
assert() is traditionally disabled in release builds, but not in
scylladb. This hasn't caused problems so far, but the latest abseil
release includes a commit [1] that causes a 1000 insn/op regression when
NDEBUG is not defined.
Clearly, we must move towards a build system where NDEBUG is defined in
release builds. But we can't just define it blindly without vetting
all the assert() calls, as some were written with the expectation that
they are enabled in release mode.
To solve the conundrum, change all assert() calls to a new SCYLLA_ASSERT()
macro in utils/assert.hh. This macro is always defined and is not conditional
on NDEBUG, so we can later (after vetting Seastar) enable NDEBUG in release
mode.
[1] 66ef711d68
Closes scylladb/scylladb#20006
443 lines
15 KiB
C++
443 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2020-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include <seastar/core/app-template.hh>
|
|
#include <seastar/core/thread.hh>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
#include <random>
|
|
#include <fmt/core.h>
|
|
#include "perf.hh"
|
|
|
|
using per_key_t = int64_t;
|
|
|
|
struct perf_key_compare {
|
|
bool operator()(const per_key_t& a, const per_key_t& b) const noexcept { return a < b; }
|
|
int64_t simplify_key(per_key_t k) noexcept {
|
|
return k;
|
|
}
|
|
};
|
|
|
|
struct perf_key_tri_compare {
|
|
std::strong_ordering operator()(const per_key_t& a, const per_key_t& b) const noexcept {
|
|
return a <=> b;
|
|
}
|
|
};
|
|
|
|
#include "utils/assert.hh"
|
|
#include "utils/bptree.hh"
|
|
|
|
using namespace seastar;
|
|
|
|
/* On node size 32 and less linear search works better */
|
|
using test_bplus_tree = bplus::tree<per_key_t, unsigned long, perf_key_compare, 4, bplus::key_search::linear>;
|
|
|
|
static_assert(bplus::SimpleLessCompare<int64_t, perf_key_compare>);
|
|
|
|
#include "utils/intrusive_btree.hh"
|
|
|
|
class perf_intrusive_key {
|
|
per_key_t _k;
|
|
public:
|
|
intrusive_b::member_hook hook;
|
|
|
|
explicit perf_intrusive_key(per_key_t v) noexcept : _k(v) {}
|
|
perf_intrusive_key(perf_intrusive_key&&) noexcept = default;
|
|
perf_intrusive_key(const perf_intrusive_key&) = delete;
|
|
|
|
struct tri_compare {
|
|
perf_key_tri_compare _cmp;
|
|
std::strong_ordering operator()(const per_key_t a, const per_key_t b) const noexcept { return _cmp(a, b); }
|
|
std::strong_ordering operator()(const perf_intrusive_key& a, const perf_intrusive_key& b) const noexcept { return _cmp(a._k, b._k); }
|
|
std::strong_ordering operator()(const per_key_t a, const perf_intrusive_key& b) const noexcept { return _cmp(a, b._k); }
|
|
std::strong_ordering operator()(const perf_intrusive_key& a, const per_key_t b) const noexcept { return _cmp(a._k, b); }
|
|
};
|
|
};
|
|
|
|
using test_b_tree = intrusive_b::tree<perf_intrusive_key, &perf_intrusive_key::hook, perf_intrusive_key::tri_compare, 12, 18, intrusive_b::key_search::linear>;
|
|
|
|
class collection_tester {
|
|
public:
|
|
virtual void insert(per_key_t k) = 0;
|
|
virtual void lower_bound(per_key_t k) = 0;
|
|
virtual void scan(int batch) = 0;
|
|
virtual void erase(per_key_t k) = 0;
|
|
virtual void drain(int batch) = 0;
|
|
virtual void clear() = 0;
|
|
virtual void clone() = 0;
|
|
virtual void show_stats() = 0;
|
|
virtual void insert_and_erase(per_key_t k) = 0;
|
|
virtual ~collection_tester() {};
|
|
};
|
|
|
|
template <typename Col>
|
|
void scan_collection(Col& c, int batch) {
|
|
int x = 0;
|
|
auto i = c.begin();
|
|
while (i != c.end()) {
|
|
i++;
|
|
if (++x % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "utils/bptree.hh"
|
|
|
|
class bptree_tester : public collection_tester {
|
|
/* On node size 32 (this test) linear search works better */
|
|
using test_tree = bplus::tree<per_key_t, unsigned long, perf_key_compare, 4, bplus::key_search::linear>;
|
|
|
|
test_tree _t;
|
|
public:
|
|
bptree_tester() : _t(perf_key_compare{}) {}
|
|
virtual void insert(per_key_t k) override { _t.emplace(k, 0); }
|
|
virtual void lower_bound(per_key_t k) override {
|
|
auto i = _t.lower_bound(k);
|
|
SCYLLA_ASSERT(i != _t.end());
|
|
}
|
|
virtual void scan(int batch) override {
|
|
scan_collection(_t, batch);
|
|
}
|
|
virtual void erase(per_key_t k) override { _t.erase(k); }
|
|
virtual void drain(int batch) override {
|
|
int x = 0;
|
|
auto i = _t.begin();
|
|
while (i != _t.end()) {
|
|
i = i.erase(perf_key_compare{});
|
|
if (++x % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
}
|
|
virtual void clear() override { _t.clear(); }
|
|
virtual void clone() override { }
|
|
virtual void insert_and_erase(per_key_t k) override {
|
|
auto i = _t.emplace(k, 0);
|
|
SCYLLA_ASSERT(i.second);
|
|
i.first.erase(perf_key_compare{});
|
|
}
|
|
virtual void show_stats() override {
|
|
struct bplus::stats st = _t.get_stats();
|
|
fmt::print("nodes: {}\n", st.nodes);
|
|
for (int i = 0; i < (int)st.nodes_filled.size(); i++) {
|
|
fmt::print(" {}: {} ({}%)\n", i, st.nodes_filled[i], st.nodes_filled[i] * 100 / st.nodes);
|
|
}
|
|
fmt::print("leaves: {}\n", st.leaves);
|
|
for (int i = 0; i < (int)st.leaves_filled.size(); i++) {
|
|
fmt::print(" {}: {} ({}%)\n", i, st.leaves_filled[i], st.leaves_filled[i] * 100 / st.leaves);
|
|
}
|
|
fmt::print("datas: {}\n", st.datas);
|
|
}
|
|
virtual ~bptree_tester() { clear(); }
|
|
};
|
|
|
|
#include "utils/compact-radix-tree.hh"
|
|
|
|
class radix_tester : public collection_tester {
|
|
public:
|
|
using test_tree = compact_radix_tree::tree<unsigned long>;
|
|
|
|
private:
|
|
test_tree _t;
|
|
public:
|
|
radix_tester() : _t() {}
|
|
virtual void insert(per_key_t k) override { _t.emplace(k, 0); }
|
|
virtual void lower_bound(per_key_t k) override {
|
|
auto i = _t.get(k);
|
|
SCYLLA_ASSERT(i != nullptr);
|
|
}
|
|
virtual void scan(int batch) override {
|
|
scan_collection(_t, batch);
|
|
}
|
|
virtual void erase(per_key_t k) override { _t.erase(k); }
|
|
virtual void drain(int batch) override {
|
|
int x = 0;
|
|
while (!_t.empty()) {
|
|
_t.erase(_t.begin().key());
|
|
if (++x % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
}
|
|
virtual void clear() override { _t.clear(); }
|
|
virtual void clone() override {
|
|
test_tree ct;
|
|
ct.clone_from(_t, [] (unsigned, const unsigned long& data) { return data; });
|
|
}
|
|
virtual void insert_and_erase(per_key_t k) override {
|
|
_t.emplace(k, 0);
|
|
_t.erase(k);
|
|
}
|
|
void show_node_stats(std::string typ, typename test_tree::stats::node_stats& st) {
|
|
fmt::print("{}: indirect: {}/{}/{}/{} direct: static {} dynamic {}\n", typ,
|
|
st.indirect_tiny, st.indirect_small, st.indirect_medium, st.indirect_large,
|
|
st.direct_static, st.direct_dynamic);
|
|
}
|
|
virtual void show_stats() override {
|
|
test_tree::stats st = _t.get_stats();
|
|
show_node_stats("inner", st.inners);
|
|
show_node_stats(" leaf", st.leaves);
|
|
}
|
|
virtual ~radix_tester() override { clear(); }
|
|
};
|
|
|
|
class btree_tester : public collection_tester {
|
|
test_b_tree _t;
|
|
perf_intrusive_key::tri_compare _cmp;
|
|
public:
|
|
btree_tester() : _t() {}
|
|
virtual void insert(per_key_t k) override { _t.insert(std::make_unique<perf_intrusive_key>(k), _cmp); }
|
|
virtual void lower_bound(per_key_t k) override {
|
|
auto i = _t.lower_bound(k, _cmp);
|
|
SCYLLA_ASSERT(i != _t.end());
|
|
}
|
|
virtual void erase(per_key_t k) override { _t.erase_and_dispose(k, _cmp, [] (perf_intrusive_key* k) noexcept { delete k; }); }
|
|
virtual void drain(int batch) override {
|
|
int x = 0;
|
|
perf_intrusive_key* k;
|
|
while ((k = _t.unlink_leftmost_without_rebalance()) != nullptr) {
|
|
delete k;
|
|
if (++x % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
}
|
|
virtual void scan(int batch) override {
|
|
scan_collection(_t, batch);
|
|
}
|
|
virtual void clear() override {
|
|
_t.clear_and_dispose([] (perf_intrusive_key* k) noexcept { delete k; });
|
|
}
|
|
virtual void clone() override { }
|
|
virtual void insert_and_erase(per_key_t k) override {
|
|
auto i = _t.insert_before(_t.end(), std::make_unique<perf_intrusive_key>(k));
|
|
_t.erase_and_dispose(i, [] (perf_intrusive_key* k) noexcept { delete k; });
|
|
}
|
|
virtual void show_stats() override {
|
|
struct intrusive_b::stats st = _t.get_stats();
|
|
fmt::print("nodes: {}\n", st.nodes);
|
|
for (int i = 0; i < (int)st.nodes_filled.size(); i++) {
|
|
fmt::print(" {}: {} ({}%)\n", i, st.nodes_filled[i], st.nodes_filled[i] * 100 / st.nodes);
|
|
}
|
|
fmt::print("leaves: {}\n", st.leaves);
|
|
for (int i = 0; i < (int)st.leaves_filled.size(); i++) {
|
|
fmt::print(" {}: {} ({}%)\n", i, st.leaves_filled[i], st.leaves_filled[i] * 100 / st.leaves);
|
|
}
|
|
}
|
|
virtual ~btree_tester() {
|
|
_t.clear();
|
|
}
|
|
};
|
|
|
|
class set_tester : public collection_tester {
|
|
std::set<per_key_t> _s;
|
|
public:
|
|
virtual void insert(per_key_t k) override { _s.insert(k); }
|
|
virtual void lower_bound(per_key_t k) override {
|
|
auto i = _s.lower_bound(k);
|
|
SCYLLA_ASSERT(i != _s.end());
|
|
}
|
|
virtual void scan(int batch) override {
|
|
scan_collection(_s, batch);
|
|
}
|
|
virtual void erase(per_key_t k) override { _s.erase(k); }
|
|
virtual void drain(int batch) override {
|
|
int x = 0;
|
|
auto i = _s.begin();
|
|
while (i != _s.end()) {
|
|
i = _s.erase(i);
|
|
if (++x % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
}
|
|
virtual void clear() override { _s.clear(); }
|
|
virtual void clone() override { }
|
|
virtual void insert_and_erase(per_key_t k) override {
|
|
auto i = _s.insert(k);
|
|
SCYLLA_ASSERT(i.second);
|
|
_s.erase(i.first);
|
|
}
|
|
virtual void show_stats() override { }
|
|
virtual ~set_tester() = default;
|
|
};
|
|
|
|
class map_tester : public collection_tester {
|
|
std::map<per_key_t, unsigned long> _m;
|
|
public:
|
|
virtual void insert(per_key_t k) override { _m[k] = 0; }
|
|
virtual void lower_bound(per_key_t k) override {
|
|
auto i = _m.lower_bound(k);
|
|
SCYLLA_ASSERT(i != _m.end());
|
|
}
|
|
virtual void scan(int batch) override {
|
|
scan_collection(_m, batch);
|
|
}
|
|
virtual void erase(per_key_t k) override { _m.erase(k); }
|
|
virtual void drain(int batch) override {
|
|
int x = 0;
|
|
auto i = _m.begin();
|
|
while (i != _m.end()) {
|
|
i = _m.erase(i);
|
|
if (++x % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
}
|
|
virtual void clear() override { _m.clear(); }
|
|
virtual void clone() override { }
|
|
virtual void insert_and_erase(per_key_t k) override {
|
|
auto i = _m.insert({k, 0});
|
|
SCYLLA_ASSERT(i.second);
|
|
_m.erase(i.first);
|
|
}
|
|
virtual void show_stats() override { }
|
|
virtual ~map_tester() = default;
|
|
};
|
|
|
|
int main(int argc, char **argv) {
|
|
namespace bpo = boost::program_options;
|
|
app_template app;
|
|
app.add_options()
|
|
("count", bpo::value<int>()->default_value(5000000), "number of keys to fill the tree with")
|
|
("batch", bpo::value<int>()->default_value(50), "number of operations between deferring points")
|
|
("iters", bpo::value<int>()->default_value(1), "number of iterations")
|
|
("col", bpo::value<std::string>()->default_value("bptree"), "collection to test")
|
|
("test", bpo::value<std::string>()->default_value("erase"), "what to test (erase, drain, clear, find, oneshot)")
|
|
("stats", bpo::value<bool>()->default_value(false), "show stats");
|
|
|
|
return app.run(argc, argv, [&app] {
|
|
auto count = app.configuration()["count"].as<int>();
|
|
auto iters = app.configuration()["iters"].as<int>();
|
|
auto batch = app.configuration()["batch"].as<int>();
|
|
auto col = app.configuration()["col"].as<std::string>();
|
|
auto tst = app.configuration()["test"].as<std::string>();
|
|
auto stats = app.configuration()["stats"].as<bool>();
|
|
|
|
return seastar::async([count, iters, batch, col, tst, stats] {
|
|
std::unique_ptr<collection_tester> c;
|
|
|
|
if (col == "bptree") {
|
|
c = std::make_unique<bptree_tester>();
|
|
} else if (col == "btree") {
|
|
c = std::make_unique<btree_tester>();
|
|
} else if (col == "set") {
|
|
c = std::make_unique<set_tester>();
|
|
} else if (col == "map") {
|
|
c = std::make_unique<map_tester>();
|
|
} else if (col == "radix") {
|
|
c = std::make_unique<radix_tester>();
|
|
} else {
|
|
fmt::print("Unknown collection\n");
|
|
return;
|
|
}
|
|
|
|
std::vector<per_key_t> keys;
|
|
|
|
for (per_key_t i = 0; i < count; i++) {
|
|
keys.push_back(i + 1);
|
|
}
|
|
|
|
std::random_device rd;
|
|
std::mt19937 g(rd());
|
|
|
|
fmt::print("Inserting {:d} k:v pairs into {} {:d} times\n", count, col, iters);
|
|
|
|
for (auto rep = 0; rep < iters; rep++) {
|
|
std::shuffle(keys.begin(), keys.end(), g);
|
|
seastar::thread::yield();
|
|
|
|
if (tst == "oneshot") {
|
|
auto d = duration_in_seconds([&] {
|
|
for (int i = 0; i < count; i++) {
|
|
c->insert_and_erase(keys[i]);
|
|
if ((i + 1) % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
});
|
|
|
|
fmt::print("one-shot: {:.6f} ms\n", d.count() * 1000);
|
|
continue;
|
|
}
|
|
|
|
auto d = duration_in_seconds([&] {
|
|
for (int i = 0; i < count; i++) {
|
|
c->insert(keys[i]);
|
|
if ((i + 1) % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
});
|
|
|
|
fmt::print("fill: {:.6f} ms\n", d.count() * 1000);
|
|
|
|
if (stats) {
|
|
c->show_stats();
|
|
}
|
|
|
|
if (tst == "erase") {
|
|
std::shuffle(keys.begin(), keys.end(), g);
|
|
seastar::thread::yield();
|
|
|
|
d = duration_in_seconds([&] {
|
|
for (int i = 0; i < count; i++) {
|
|
c->erase(keys[i]);
|
|
if ((i + 1) % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
});
|
|
|
|
fmt::print("erase: {:.6f} ms\n", d.count() * 1000);
|
|
} else if (tst == "drain") {
|
|
d = duration_in_seconds([&] {
|
|
c->drain(batch);
|
|
});
|
|
|
|
fmt::print("drain: {:.6f} ms\n", d.count() * 1000);
|
|
} else if (tst == "clear") {
|
|
d = duration_in_seconds([&] {
|
|
c->clear();
|
|
});
|
|
|
|
fmt::print("clear: {:.6f} ms\n", d.count() * 1000);
|
|
} else if (tst == "find") {
|
|
std::shuffle(keys.begin(), keys.end(), g);
|
|
seastar::thread::yield();
|
|
|
|
d = duration_in_seconds([&] {
|
|
for (int i = 0; i < count; i++) {
|
|
c->lower_bound(keys[i]);
|
|
if ((i + 1) % batch == 0) {
|
|
seastar::thread::yield();
|
|
}
|
|
}
|
|
});
|
|
|
|
fmt::print("find: {:.6f} ms\n", d.count() * 1000);
|
|
} else if (tst == "scan") {
|
|
d = duration_in_seconds([&] {
|
|
c->scan(batch);
|
|
});
|
|
|
|
fmt::print("scan: {:.6f} ms\n", d.count() * 1000);
|
|
} else if (tst == "clone") {
|
|
d = duration_in_seconds([&] {
|
|
c->clone();
|
|
});
|
|
fmt::print("clone: {:.6f} ms\n", d.count() * 1000);
|
|
}
|
|
|
|
c->clear();
|
|
}
|
|
});
|
|
});
|
|
}
|