#include "broker/alm/multipath.hh" #include "broker/alm/routing_table.hh" #include "broker/endpoint.hh" #include #include #include #include using broker::endpoint_id; using broker::vector_timestamp; namespace { struct linear_routing_table_row { using path_type = std::vector; using versioned_path_type = std::pair; endpoint_id id; std::vector versioned_paths; linear_routing_table_row() = default; linear_routing_table_row(linear_routing_table_row&&) = default; linear_routing_table_row(const linear_routing_table_row&) = default; linear_routing_table_row& operator=(linear_routing_table_row&&) = default; linear_routing_table_row& operator=(const linear_routing_table_row&) = default; explicit linear_routing_table_row(endpoint_id id) : id(std::move(id)) { versioned_paths.reserve(32); } }; struct linear_routing_table { std::vector rows; const std::vector* shortest_path(const endpoint_id& peer) const { auto pred = [&peer](const auto& row) { return row.id == peer; }; if (auto i = std::find_if(rows.begin(), rows.end(), pred); i != rows.end()) return std::addressof(i->versioned_paths.front().first); else return nullptr; } linear_routing_table_row* find_row(const endpoint_id& peer) { auto pred = [&peer](const auto& row) { return row.id == peer; }; if (auto i = std::find_if(rows.begin(), rows.end(), pred); i != rows.end()) return std::addressof(*i); else return nullptr; } linear_routing_table_row& operator[](const endpoint_id& peer) { auto pred = [&peer](const auto& row) { return row.id == peer; }; if (auto i = std::find_if(rows.begin(), rows.end(), pred); i != rows.end()) { return *i; } else { rows.emplace_back(peer); return rows.back(); } } }; auto shortest_path(const linear_routing_table& tbl, const endpoint_id& peer) { return tbl.shortest_path(peer); } struct sorted_linear_routing_table { std::vector rows; struct row_less_t { bool operator()(const endpoint_id& x, const linear_routing_table_row& y) const noexcept { return x < y.id; } bool operator()(const linear_routing_table_row& x, const endpoint_id& y) const noexcept { return x.id < y; } }; static constexpr auto row_less = row_less_t{}; const std::vector* shortest_path(const endpoint_id& peer) const { if (auto i = std::lower_bound(rows.begin(), rows.end(), peer, row_less); i != rows.end() && i->id == peer) return std::addressof(i->versioned_paths.front().first); else return nullptr; } linear_routing_table_row* find_row(const endpoint_id& peer) { if (auto i = std::lower_bound(rows.begin(), rows.end(), peer, row_less); i != rows.end() && i->id == peer) return std::addressof(*i); else return nullptr; } linear_routing_table_row& operator[](const endpoint_id& peer) { if (auto i = std::lower_bound(rows.begin(), rows.end(), peer, row_less); i != rows.end()) { if (i->id == peer) { return *i; } else { return *rows.emplace(i, peer); } } else { rows.emplace_back(peer); return rows.back(); } } }; auto shortest_path(const sorted_linear_routing_table& tbl, const endpoint_id& peer) { return tbl.shortest_path(peer); } template bool add_or_update_path_impl(TableType& tbl, const endpoint_id& peer, std::vector&& path, vector_timestamp&& ts) { auto& row = tbl[peer]; auto& paths = row.versioned_paths; auto i = std::lower_bound(paths.begin(), paths.end(), path, broker::alm::path_less); if (i == paths.end()) { paths.emplace_back(std::move(path), std::move(ts)); return true; } else if (i->first != path) { paths.insert(i, std::make_pair(std::move(path), std::move(ts))); return true; } else { if (i->second < ts) i->second = std::move(ts); return false; } } bool add_or_update_path(linear_routing_table& tbl, const endpoint_id& peer, std::vector path, vector_timestamp ts) { return add_or_update_path_impl(tbl, peer, std::move(path), std::move(ts)); } bool add_or_update_path(sorted_linear_routing_table& tbl, const endpoint_id& peer, std::vector path, vector_timestamp ts) { return add_or_update_path_impl(tbl, peer, std::move(path), std::move(ts)); } // For the benchmarking, we use a simple tree structure topology: // // . . . .. . . . // \ / \ / \ / \ / // [n = 2] O --- O O --- O // \ / \ / // \ / \ / // [n = 1] O ------- O // \ / // \ / // \ / // \ / // [this node] O // // This results in a "network" with (0 > n > 11): // - nodes: f(1) = 2, f(n) = f(n-1) + 2^n (2, 6, 14, ...) // - connections: f(1) = 3, f(n) = f(n-1) + 2^(n-1)*3 (3, 9, 21, ...) using path_type = std::vector; struct id_generator { using array_type = broker::endpoint_id::array_type; id_generator() : rng(0xB7E57) { // nop } endpoint_id next() { std::uniform_int_distribution<> d{0, 255}; array_type bytes; for (auto& x : bytes) x = static_cast(d(rng)); return broker::endpoint_id(bytes); } std::minstd_rand rng; }; template class routing_table : public benchmark::Fixture { public: using table_type = TableType; routing_table() { topologies.resize(10); // v2_topologies.resize(10); ids.resize(10); receivers_10p.resize(10); id_generator g; auto on_new_node = [this](size_t level, const endpoint_id& leaf_id) { ids[level].emplace_back(leaf_id); }; for (size_t index = 0; index < 10; ++index) fill_tbl(topologies[index], g, {}, 0, index, on_new_node); // for (size_t index = 0; index < 10; ++index) // fill_tbl(v2_topologies[index], g, {}, 0, index, [](auto&&...) {}); // Sanity checking #ifndef NDEBUG for (size_t index = 0; index < 10; ++index) assert(ids[index].size() == (1 << (index + 1))); #endif std::vector flat_ids; for (size_t index = 0; index < 10; ++index) { auto& vec = ids[index]; flat_ids.insert(flat_ids.end(), vec.begin(), vec.end()); std::shuffle(flat_ids.begin(), flat_ids.end(), g.rng); auto p10 = std::max(flat_ids.size() / 10, size_t{1}); receivers_10p[index].assign(flat_ids.begin(), flat_ids.begin() + p10); } } // Topologies by level. std::vector topologies; // IDs by level. std::vector> ids; // Receivers for the different generate_paths setup (10 percent). std::vector> receivers_10p; static auto make_vt(size_t n) { broker::vector_timestamp result; result.resize(n); return result; } static auto make_vt(const path_type& p) { return make_vt(p.size()); } template static void fill_tbl(Table& tbl, id_generator& g, const path_type& p, size_t level, size_t max_level, OnNewNode on_new_node) { auto next_path = [](const auto& src, auto id) { auto result = src; result.emplace_back(id); return result; }; auto add_entry = [&](const auto& id, const path_type& new_path) { add_or_update_path(tbl, id, new_path, make_vt(new_path)); }; // Add first leaf node. auto leaf1_id = g.next(); auto leaf1_path = next_path(p, leaf1_id); on_new_node(level, leaf1_id); add_entry(leaf1_id, leaf1_path); // Add second leaf node. auto leaf2_id = g.next(); auto leaf2_path = next_path(p, leaf2_id); on_new_node(level, leaf2_id); add_entry(leaf2_id, leaf2_path); // Add paths between leaf1 and leaf2. add_entry(leaf2_id, next_path(leaf1_path, leaf2_id)); add_entry(leaf1_id, next_path(leaf2_path, leaf1_id)); // Enter next level. if (level < max_level) { fill_tbl(tbl, g, leaf1_path, level + 1, max_level, on_new_node); fill_tbl(tbl, g, leaf2_path, level + 1, max_level, on_new_node); } } void add_or_update_path_bench(benchmark::State& state) { auto max_level = static_cast(state.range(0)); for (auto _ : state) { id_generator g; table_type tbl; fill_tbl(tbl, g, {}, 0, max_level, [](auto&&...) {}); benchmark::ClobberMemory(); benchmark::DoNotOptimize(tbl); } } void shortest_path_bench(benchmark::State& state) { auto index = static_cast(state.range(0)); const auto& tbl = topologies[index]; const auto& id = ids[index].front(); for (auto _ : state) { auto sp = shortest_path(tbl, id); benchmark::DoNotOptimize(sp); assert(sp != nullptr); assert(sp->size() == index + 1); } } void generate_paths_1_bench(benchmark::State& state) { auto index = static_cast(state.range(0)); const auto& tbl = topologies[index]; const auto& id = ids[index].front(); std::vector receivers{id}; for (auto _ : state) { std::vector routes; std::vector unreachables; broker::alm::multipath::generate(receivers, tbl, routes, unreachables); benchmark::ClobberMemory(); benchmark::DoNotOptimize(routes); assert(unreachables.empty()); } } void generate_paths_10_bench(benchmark::State& state) { auto index = static_cast(state.range(0)); const auto& tbl = topologies[index]; const auto& receivers = receivers_10p[index]; for (auto _ : state) { std::vector routes; std::vector unreachables; broker::alm::multipath::generate(receivers, tbl, routes, unreachables); benchmark::ClobberMemory(); benchmark::DoNotOptimize(routes); assert(unreachables.empty()); } } void erase_front_bench(benchmark::State& state) { auto index = static_cast(state.range(0)); const auto& id = ids[0].front(); for (auto _ : state) { state.PauseTiming(); auto cpy = topologies[index]; state.ResumeTiming(); erase(cpy, id, [](auto&&...) {}); benchmark::ClobberMemory(); benchmark::DoNotOptimize(cpy); } } void erase_back_bench(benchmark::State& state) { auto index = static_cast(state.range(0)); const auto& id = ids[index].front(); for (auto _ : state) { state.PauseTiming(); auto cpy = topologies[index]; state.ResumeTiming(); erase(cpy, id, [](auto&&...) {}); benchmark::ClobberMemory(); benchmark::DoNotOptimize(cpy); } } }; } // namespace using default_routing_table = broker::alm::routing_table; #define BENCH_SETUP(TableType, Algorithm) \ using TableType##_impl = routing_table; \ BENCHMARK_DEFINE_F(TableType##_impl, Algorithm) \ (benchmark::State & state) { \ Algorithm##_bench(state); \ } \ BENCHMARK_REGISTER_F(TableType##_impl, Algorithm)->DenseRange(0, 9, 1); // -- adding entries to a routing table ---------------------------------------- BENCH_SETUP(default_routing_table, add_or_update_path) BENCH_SETUP(default_routing_table, shortest_path) BENCH_SETUP(default_routing_table, generate_paths_1) BENCH_SETUP(default_routing_table, generate_paths_10) BENCH_SETUP(default_routing_table, erase_front) BENCH_SETUP(default_routing_table, erase_back) BENCH_SETUP(linear_routing_table, add_or_update_path) BENCH_SETUP(linear_routing_table, shortest_path) // BENCH_SETUP(linear_routing_table, generate_paths_1) // BENCH_SETUP(linear_routing_table, generate_paths_10) // BENCH_SETUP(linear_routing_table, erase_front) // BENCH_SETUP(linear_routing_table, erase_back) BENCH_SETUP(sorted_linear_routing_table, add_or_update_path) BENCH_SETUP(sorted_linear_routing_table, shortest_path) // BENCH_SETUP(sorted_linear_routing_table, generate_paths_1) // BENCH_SETUP(sorted_linear_routing_table, generate_paths_10) // BENCH_SETUP(sorted_linear_routing_table, erase_front) // BENCH_SETUP(sorted_linear_routing_table, erase_back)