Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions bench-keylen-abseil.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Variable key length benchmark for abseil flat_hash_map.
#include <cstdint>
#include <cstdio>
#include <algorithm>
#include <chrono>
#include <vector>
#include <string>
#include <string_view>
#include "absl/container/flat_hash_map.h"

template <typename T>
inline void do_not_optimize(T const& val) {
asm volatile("" : : "r,m"(val) : "memory");
}

static uint64_t splitmix64(uint64_t& state) {
state += 0x9e3779b97f4a7c15;
uint64_t z = state;
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}

constexpr uint64_t KEY_SEED = 0xDEADBEEF12345678;
constexpr uint64_t MISS_SEED = 0xCAFEBABE87654321;
constexpr int TOTAL_RUNS = 10;
constexpr int WARMUP = 2;
constexpr int MEASURED = TOTAL_RUNS - WARMUP;
constexpr size_t N = 1048576;
constexpr size_t FILL = N / 2;

static const char hex_chars[] = "0123456789abcdef";

static void fill_hex(uint64_t val, char* buf, size_t len) {
uint64_t v = val;
for (size_t i = 0; i < len; i++) {
if (i % 16 == 0 && i > 0) v = v * 0x9e3779b97f4a7c15;
buf[i] = hex_chars[v & 0xF];
v >>= 4;
if (v == 0) v = val * (i + 1);
}
}

static uint64_t median_val(uint64_t* arr, int n) {
std::sort(arr, arr + n);
return arr[n / 2];
}

static void bench_keylen(size_t key_len) {
// Generate keys
std::vector<std::string> keys(FILL);
std::vector<std::string> miss_keys(FILL);
uint64_t ks = KEY_SEED;
uint64_t ms = MISS_SEED;
for (size_t i = 0; i < FILL; i++) {
keys[i].resize(key_len);
miss_keys[i].resize(key_len);
fill_hex(splitmix64(ks), keys[i].data(), key_len);
fill_hex(splitmix64(ms), miss_keys[i].data(), key_len);
}

// Shuffle orders
std::vector<size_t> hit_order(FILL), miss_order(FILL);
for (size_t i = 0; i < FILL; i++) { hit_order[i] = i; miss_order[i] = i; }
uint64_t rng1 = 42, rng2 = 99;
for (size_t i = FILL - 1; i > 0; i--) {
size_t j = splitmix64(rng1) % (i + 1);
std::swap(hit_order[i], hit_order[j]);
j = splitmix64(rng2) % (i + 1);
std::swap(miss_order[i], miss_order[j]);
}

uint64_t hit_times[MEASURED], miss_times[MEASURED];

for (int r = 0; r < TOTAL_RUNS; r++) {
absl::flat_hash_map<std::string_view, uint64_t> map;
map.reserve(N);
for (size_t i = 0; i < FILL; i++)
map.emplace(std::string_view(keys[i]), i);

auto start = std::chrono::steady_clock::now();
for (size_t i = 0; i < FILL; i++) {
auto it = map.find(std::string_view(keys[hit_order[i]]));
do_not_optimize(it->second);
}
auto end = std::chrono::steady_clock::now();
uint64_t hit_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

start = std::chrono::steady_clock::now();
for (size_t i = 0; i < FILL; i++) {
auto it = map.find(std::string_view(miss_keys[miss_order[i]]));
do_not_optimize(it);
}
end = std::chrono::steady_clock::now();
uint64_t miss_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

if (r >= WARMUP) {
hit_times[r - WARMUP] = hit_us;
miss_times[r - WARMUP] = miss_us;
}
}

printf("KEYLEN\tlen=%zu\thit_shuffled=%llu\tmiss_shuffled=%llu\n",
key_len, median_val(hit_times, MEASURED), median_val(miss_times, MEASURED));
}

int main() {
printf("=== Abseil Variable Key Length (n=%zu, 50%% load, shuffled) ===\n\n", N);
size_t lens[] = {8, 16, 32, 64, 128, 256};
for (size_t kl : lens) bench_keylen(kl);
printf("\nDONE\n");
return 0;
}
146 changes: 146 additions & 0 deletions bench-mixed-abseil.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Mixed workload benchmark for abseil flat_hash_map.
// Same operation distribution as autobench-mixed.zig:
// 40% hit, 40% miss, 10% insert, 10% delete.
#include <cstdint>
#include <cstdio>
#include <algorithm>
#include <chrono>
#include <vector>
#include <string_view>
#include "absl/container/flat_hash_map.h"

template <typename T>
inline void do_not_optimize(T const& val) {
asm volatile("" : : "r,m"(val) : "memory");
}

static uint64_t splitmix64(uint64_t& state) {
state += 0x9e3779b97f4a7c15;
uint64_t z = state;
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}

constexpr uint64_t KEY_SEED = 0xDEADBEEF12345678;
constexpr uint64_t MISS_SEED = 0xCAFEBABE87654321;
constexpr uint64_t OP_SEED = 0x1234ABCD5678EF01;
constexpr int TOTAL_RUNS = 8;
constexpr int WARMUP = 2;
constexpr int MEASURED = TOTAL_RUNS - WARMUP;
constexpr size_t KEY_LEN = 16;
constexpr size_t N = 1048576;
constexpr size_t OPS_PER_RUN = 1000000;

static const char hex_chars[] = "0123456789abcdef";

static void u64_to_hex(uint64_t val, char* buf) {
for (int i = KEY_LEN - 1; i >= 0; i--) {
buf[i] = hex_chars[val & 0xF];
val >>= 4;
}
}

static uint64_t median_val(uint64_t* arr, int n) {
std::sort(arr, arr + n);
return arr[n / 2];
}

int main() {
printf("=== Abseil Mixed Workload Benchmark ===\n");
printf("=== %zu ops per run, 40%% hit / 40%% miss / 10%% insert / 10%% delete ===\n", OPS_PER_RUN);

// Pre-generate key pool
size_t key_pool_size = N * 2;
std::vector<char> key_pool_buf(key_pool_size * KEY_LEN);
uint64_t ks = KEY_SEED;
for (size_t i = 0; i < key_pool_size; i++)
u64_to_hex(splitmix64(ks), &key_pool_buf[i * KEY_LEN]);

std::vector<std::string_view> key_pool(key_pool_size);
for (size_t i = 0; i < key_pool_size; i++)
key_pool[i] = std::string_view(&key_pool_buf[i * KEY_LEN], KEY_LEN);

// Miss keys
std::vector<char> miss_buf(OPS_PER_RUN * KEY_LEN);
uint64_t ms = MISS_SEED;
for (size_t i = 0; i < OPS_PER_RUN; i++)
u64_to_hex(splitmix64(ms), &miss_buf[i * KEY_LEN]);

std::vector<std::string_view> miss_keys(OPS_PER_RUN);
for (size_t i = 0; i < OPS_PER_RUN; i++)
miss_keys[i] = std::string_view(&miss_buf[i * KEY_LEN], KEY_LEN);

// Operation sequence
std::vector<uint8_t> ops(OPS_PER_RUN);
uint64_t op_rng = OP_SEED;
for (size_t i = 0; i < OPS_PER_RUN; i++)
ops[i] = splitmix64(op_rng) % 10;

// Random indices
std::vector<uint64_t> rand_indices(OPS_PER_RUN);
uint64_t idx_rng = 0xFEDCBA9876543210;
for (size_t i = 0; i < OPS_PER_RUN; i++)
rand_indices[i] = splitmix64(idx_rng);

int load_pcts[] = {25, 50, 75};

for (int pct : load_pcts) {
size_t fill = N * pct / 100;
uint64_t times[MEASURED];

for (int r = 0; r < TOTAL_RUNS; r++) {
absl::flat_hash_map<std::string_view, uint64_t> map;
map.reserve(N);

for (size_t i = 0; i < fill; i++)
map.emplace(key_pool[i], i);

size_t live_count = fill;
size_t next_insert = fill;
size_t miss_idx = 0;

auto start = std::chrono::steady_clock::now();

for (size_t i = 0; i < OPS_PER_RUN; i++) {
uint8_t op = ops[i];
if (op < 4) {
if (live_count > 0) {
size_t ki = rand_indices[i] % live_count;
auto it = map.find(key_pool[ki]);
if (it != map.end()) do_not_optimize(it->second);
}
} else if (op < 8) {
auto it = map.find(miss_keys[miss_idx % OPS_PER_RUN]);
do_not_optimize(it);
miss_idx++;
} else if (op == 8) {
if (next_insert < key_pool_size) {
map.emplace(key_pool[next_insert], next_insert);
next_insert++;
live_count++;
}
} else {
if (live_count > 0) {
size_t ki = rand_indices[i] % live_count;
map.erase(key_pool[ki]);
}
}
}

auto end = std::chrono::steady_clock::now();
uint64_t total_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

if (r >= WARMUP) {
times[r - WARMUP] = total_us;
}
}

uint64_t med = median_val(times, MEASURED);
uint64_t ops_sec = OPS_PER_RUN * 1000000ULL / med;
printf("MIXED\tload=%d\ttotal_us=%llu\tops_per_sec=%llu\n", pct, med, ops_sec);
}

printf("\nDONE\n");
return 0;
}
65 changes: 65 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,69 @@ pub fn build(b: *std.Build) void {
const run_autobench_verify = b.addRunArtifact(autobench_verify);
const autobench_verify_step = b.step("autobench-strings-verify", "Run shuffled verification");
autobench_verify_step.dependOn(&run_autobench_verify.step);

// Hit + miss benchmark with shuffled access
const autobench_miss = b.addExecutable(.{
.name = "autobench-miss",
.root_module = b.createModule(.{
.root_source_file = b.path("src/autobench-miss.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_autobench_miss = b.addRunArtifact(autobench_miss);
const autobench_miss_step = b.step("autobench-miss", "Run hit + miss shuffled benchmark");
autobench_miss_step.dependOn(&run_autobench_miss.step);

// Tombstone churn test
const autobench_churn = b.addExecutable(.{
.name = "autobench-churn",
.root_module = b.createModule(.{
.root_source_file = b.path("src/autobench-churn.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_autobench_churn = b.addRunArtifact(autobench_churn);
const autobench_churn_step = b.step("autobench-churn", "Run tombstone churn test");
autobench_churn_step.dependOn(&run_autobench_churn.step);

// Mixed workload benchmark
const autobench_mixed = b.addExecutable(.{
.name = "autobench-mixed",
.root_module = b.createModule(.{
.root_source_file = b.path("src/autobench-mixed.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_autobench_mixed = b.addRunArtifact(autobench_mixed);
const autobench_mixed_step = b.step("autobench-mixed", "Run mixed workload benchmark");
autobench_mixed_step.dependOn(&run_autobench_mixed.step);

// Memory overhead
const autobench_memory = b.addExecutable(.{
.name = "autobench-memory",
.root_module = b.createModule(.{
.root_source_file = b.path("src/autobench-memory.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_autobench_memory = b.addRunArtifact(autobench_memory);
const autobench_memory_step = b.step("autobench-memory", "Report memory overhead");
autobench_memory_step.dependOn(&run_autobench_memory.step);

// Variable key length
const autobench_keylen = b.addExecutable(.{
.name = "autobench-keylen",
.root_module = b.createModule(.{
.root_source_file = b.path("src/autobench-keylen.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_autobench_keylen = b.addRunArtifact(autobench_keylen);
const autobench_keylen_step = b.step("autobench-keylen", "Run variable key length benchmark");
autobench_keylen_step.dependOn(&run_autobench_keylen.step);
}
Loading