1
0
Fork 0
mirror of https://github.com/mfocko/blog.git synced 2025-05-04 18:32:58 +02:00

feat(algorithms): add Breaking of the Hash Table

Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
Matej Focko 2023-11-16 10:16:13 +01:00
parent a581e9753f
commit 2794519506
Signed by: mfocko
GPG key ID: 7C47D46246790496
5 changed files with 838 additions and 0 deletions
static/files/algorithms/hash-tables/breaking

View file

@ -0,0 +1,133 @@
#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;
}

View file

@ -0,0 +1,118 @@
#!/usr/bin/env python3
from functools import cached_property
from time import monotonic_ns
N_ELEMENTS = 10_000_000
LOOPS = 10
class Strategy:
def __init__(self, data_structure=set):
self._table = data_structure()
@cached_property
def elements(self):
raise NotImplementedError("Implement for each strategy")
@property
def name(self):
raise NotImplementedError("Implement for each strategy")
def run(self):
print(f"\nBenchmarking:\t\t{self.name}")
# Extract the elements here, so that the evaluation of them does not
# slow down the relevant part of benchmark
elements = self.elements
# Insertion phase
start = monotonic_ns()
for x in elements:
self._table.add(x)
after_insertion = monotonic_ns()
print(f"Insertion phase:\t{(after_insertion - start) / 1000000:.2f}ms")
# Lookup phase
start = monotonic_ns()
for _ in range(LOOPS):
for x in elements:
assert x in self._table
after_lookups = monotonic_ns()
print(f"Lookup phase:\t\t{(after_lookups - start) / 1000000:.2f}ms")
class AscendingOrderedSequence(Strategy):
@property
def name(self):
return "ordered sequence (ascending)"
@cached_property
def elements(self):
return [x for x in range(N_ELEMENTS)]
class DescendingOrderedSequence(Strategy):
@property
def name(self):
return "ordered sequence (descending)"
@cached_property
def elements(self):
return [x for x in reversed(range(N_ELEMENTS))]
class ProgressiveAttack(Strategy):
@staticmethod
def _break(n):
return n << max(5, n.bit_length())
class ProgressiveAscendingAttack(ProgressiveAttack):
@property
def name(self):
return "progressive sequence that self-heals on resize"
@cached_property
def elements(self):
return [self._break(x) for x in range(N_ELEMENTS)]
class ProgressiveDescendingAttack(ProgressiveAttack):
@property
def name(self):
return "progressive sequence that self-heals in the end"
@cached_property
def elements(self):
return [self._break(x) for x in reversed(range(N_ELEMENTS))]
class HardAttack(Strategy):
@property
def name(self):
return "carefully chosen numbers"
@cached_property
def elements(self):
return [x << 32 for x in range(N_ELEMENTS)]
STRATEGIES = [
AscendingOrderedSequence,
DescendingOrderedSequence,
ProgressiveAscendingAttack,
ProgressiveDescendingAttack,
HardAttack,
]
def main():
for strategy in STRATEGIES:
strategy().run()
if __name__ == "__main__":
main()