blog/files/algorithms/hash-tables/breaking/benchmark.cpp

134 lines
4.1 KiB
C++
Raw Normal View History

#include <bit>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <functional>
#include <iostream>
#include <ranges>
#include <set>
#include <string>
#include <unordered_set>
using elem_t = std::uint64_t;
const elem_t N_ELEMENTS = 10000000;
#define LOOPS 10
template <typename T> struct strategy {
virtual std::string name() const = 0;
virtual T elements() = 0;
template <typename C> void run(C &&s) {
using namespace std;
cout << "\nBenchmarking:\t\t" << name() << '\n';
auto start = chrono::steady_clock::now();
for (auto x : elements()) {
s.insert(x);
}
auto after_insertion = chrono::steady_clock::now();
auto insertion_time =
chrono::duration_cast<chrono::milliseconds>(after_insertion - start);
cout << "Insertion phase:\t" << insertion_time << "\n";
start = chrono::steady_clock::now();
for (int i = 0; i < LOOPS; ++i) {
for (auto x : elements()) {
assert(s.contains(x));
}
}
auto after_lookups = chrono::steady_clock::now();
auto lookup_time =
chrono::duration_cast<chrono::milliseconds>(after_lookups - start);
cout << "Lookup phase:\t\t" << lookup_time << "\n";
}
virtual ~strategy() = default;
};
using iota_t =
decltype(std::views::iota(static_cast<elem_t>(0), static_cast<elem_t>(0)));
struct ascending_ordered_sequence : public strategy<iota_t> {
std::string name() const override { return "ordered sequence (ascending)"; }
iota_t elements() override {
return std::views::iota(static_cast<elem_t>(0), N_ELEMENTS);
}
};
static elem_t reverse(elem_t x) { return static_cast<elem_t>(N_ELEMENTS) - x; }
using reversed_iota_t =
decltype(std::views::iota(static_cast<elem_t>(0), static_cast<elem_t>(0)) |
std::views::transform(reverse));
struct descending_ordered_sequence : public strategy<reversed_iota_t> {
std::string name() const override { return "ordered sequence (descending)"; }
reversed_iota_t elements() override {
return std::views::iota(static_cast<elem_t>(1), N_ELEMENTS + 1) |
std::views::transform(reverse);
}
};
static elem_t attack(elem_t x) { return x << (5 + std::bit_width(x)); }
using attacked_iota_t =
decltype(std::views::iota(static_cast<elem_t>(0), static_cast<elem_t>(0)) |
std::views::transform(attack));
struct progressive_ascending_attack : public strategy<attacked_iota_t> {
std::string name() const override {
return "progressive sequence that self-heals on resize";
}
attacked_iota_t elements() override {
return std::views::iota(static_cast<elem_t>(0), N_ELEMENTS) |
std::views::transform(attack);
}
};
using reversed_attacked_iota_t =
decltype(std::views::iota(static_cast<elem_t>(0), static_cast<elem_t>(0)) |
std::views::transform(reverse) | std::views::transform(attack));
struct progressive_descending_attack
: public strategy<reversed_attacked_iota_t> {
std::string name() const override {
return "progressive sequence that self-heals in the end";
}
reversed_attacked_iota_t elements() override {
return std::views::iota(static_cast<elem_t>(1), N_ELEMENTS + 1) |
std::views::transform(reverse) | std::views::transform(attack);
}
};
static elem_t shift(elem_t x) { return x << 32; }
using shifted_iota_t =
decltype(std::views::iota(static_cast<elem_t>(0), static_cast<elem_t>(0)) |
std::views::transform(shift));
struct hard_attack : public strategy<shifted_iota_t> {
std::string name() const override { return "carefully chosen numbers"; }
shifted_iota_t elements() override {
return std::views::iota(static_cast<elem_t>(0), N_ELEMENTS) |
std::views::transform(shift);
}
};
template <typename C> void run_all(const std::string &note) {
std::cout << "\n«" << note << "»\n";
ascending_ordered_sequence{}.run(C{});
descending_ordered_sequence{}.run(C{});
progressive_ascending_attack{}.run(C{});
progressive_descending_attack{}.run(C{});
hard_attack{}.run(C{});
}
int main() {
run_all<std::unordered_set<elem_t>>("hash table");
run_all<std::set<elem_t>>("red-black tree");
return 0;
}