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:
parent
a581e9753f
commit
2794519506
5 changed files with 838 additions and 0 deletions
static/files/algorithms/hash-tables/breaking
133
static/files/algorithms/hash-tables/breaking/benchmark.cpp
Normal file
133
static/files/algorithms/hash-tables/breaking/benchmark.cpp
Normal 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 ¬e) {
|
||||
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;
|
||||
}
|
118
static/files/algorithms/hash-tables/breaking/benchmark.py
Normal file
118
static/files/algorithms/hash-tables/breaking/benchmark.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue