Created
July 21, 2026 19:46
-
-
Save michael-grunder/e3819c48617f130be8e2c75beee75ed8 to your computer and use it in GitHub Desktop.
Granular ck_rhs_t benchmark script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2026 Michael Grunder. | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| * SUCH DAMAGE. | |
| * | |
| * compile with: | |
| * cc -o ck_rhs_mixed_benchmark -O2 -g3 ck_rhs_mixed_benchmark.c src/ck_rhs.c | |
| */ | |
| #include <ck_malloc.h> | |
| #include <ck_rhs.h> | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #define DEFAULT_OPERATIONS UINT64_C(1000000) | |
| #define INITIAL_CAPACITY 1024 | |
| #define BATCH_SIZE 1024 | |
| enum api_operation { | |
| API_INIT, | |
| API_DESTROY, | |
| API_PUT, | |
| API_SET, | |
| API_FAS, | |
| API_GET, | |
| API_REMOVE, | |
| API_COUNT, | |
| API_ITERATOR_INIT, | |
| API_NEXT, | |
| API_OPERATIONS | |
| }; | |
| enum random_operation { | |
| RANDOM_PUT, | |
| RANDOM_SET, | |
| RANDOM_FAS, | |
| RANDOM_GET, | |
| RANDOM_REMOVE, | |
| RANDOM_OPERATIONS | |
| }; | |
| enum meta_operation { | |
| META_ITERATION, | |
| META_BATCH_DELETE, | |
| META_FULL_VALIDATION, | |
| META_OPERATIONS | |
| }; | |
| struct measurement { | |
| uint64_t calls; | |
| uint64_t cycles; | |
| }; | |
| struct allocation_metrics { | |
| uint64_t requests; | |
| uint64_t requested_bytes; | |
| uint64_t frees; | |
| uint64_t freed_bytes; | |
| uint64_t live_bytes; | |
| uint64_t peak_live_bytes; | |
| }; | |
| struct key { | |
| uint64_t id; | |
| unsigned long hash; | |
| }; | |
| struct key_slot { | |
| struct key key[2]; | |
| size_t index; | |
| unsigned int version; | |
| bool active; | |
| uint64_t seen_epoch; | |
| }; | |
| struct key_pool { | |
| struct key_slot *slots; | |
| struct key_slot **active; | |
| struct key_slot **inactive; | |
| size_t capacity; | |
| size_t active_count; | |
| size_t inactive_count; | |
| }; | |
| static const char *api_names[API_OPERATIONS] = { | |
| "ck_rhs_init", | |
| "ck_rhs_destroy", | |
| "ck_rhs_put", | |
| "ck_rhs_set", | |
| "ck_rhs_fas", | |
| "ck_rhs_get", | |
| "ck_rhs_remove", | |
| "ck_rhs_count", | |
| "ck_rhs_iterator_init", | |
| "ck_rhs_next" | |
| }; | |
| static const char *random_names[RANDOM_OPERATIONS] = { | |
| "put", "set", "fas", "get", "remove" | |
| }; | |
| static const char *meta_names[META_OPERATIONS] = { | |
| "iteration", "batch delete", "full validation" | |
| }; | |
| static struct measurement api_measurements[API_OPERATIONS]; | |
| static struct measurement random_measurements[RANDOM_OPERATIONS]; | |
| static struct measurement meta_measurements[META_OPERATIONS]; | |
| static struct allocation_metrics allocation_metrics; | |
| static uint64_t rhs_seed; | |
| static uint64_t seen_epoch; | |
| static bool allocation_tracking_enabled; | |
| static bool measurement_enabled; | |
| static uint64_t | |
| random_next(uint64_t *state) | |
| { | |
| uint64_t z; | |
| z = (*state += UINT64_C(0x9e3779b97f4a7c15)); | |
| z = (z ^ (z >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); | |
| z = (z ^ (z >> 27)) * UINT64_C(0x94d049bb133111eb); | |
| return z ^ (z >> 31); | |
| } | |
| static uint64_t | |
| mix64(uint64_t value) | |
| { | |
| value = (value ^ (value >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); | |
| value = (value ^ (value >> 27)) * UINT64_C(0x94d049bb133111eb); | |
| return value ^ (value >> 31); | |
| } | |
| static uint64_t | |
| benchmark_timestamp(void) | |
| { | |
| #if defined(__x86_64__) | |
| uint32_t low, high; | |
| /* Avoid the heavyweight cpuid pair in common.h for per-call samples. */ | |
| __asm__ __volatile__("lfence; rdtsc" | |
| : "=a" (low), "=d" (high) | |
| : | |
| : "memory"); | |
| return ((uint64_t)high << 32) | low; | |
| #else | |
| return rdtsc(); | |
| #endif | |
| } | |
| static void | |
| benchmark_abort(const char *message) | |
| { | |
| fprintf(stderr, "ERROR: %s\n", message); | |
| exit(EXIT_FAILURE); | |
| } | |
| static void * | |
| benchmark_malloc(size_t size) | |
| { | |
| void *pointer; | |
| if (allocation_tracking_enabled) { | |
| allocation_metrics.requests++; | |
| allocation_metrics.requested_bytes += size; | |
| } | |
| pointer = malloc(size); | |
| if (pointer == NULL) | |
| return NULL; | |
| if (allocation_tracking_enabled) { | |
| allocation_metrics.live_bytes += size; | |
| if (allocation_metrics.live_bytes > | |
| allocation_metrics.peak_live_bytes) { | |
| allocation_metrics.peak_live_bytes = | |
| allocation_metrics.live_bytes; | |
| } | |
| } | |
| return pointer; | |
| } | |
| static void | |
| benchmark_free(void *pointer, size_t size, bool defer) | |
| { | |
| (void)defer; | |
| if (allocation_tracking_enabled) { | |
| allocation_metrics.frees++; | |
| allocation_metrics.freed_bytes += size; | |
| if (size > allocation_metrics.live_bytes) | |
| benchmark_abort("allocator byte accounting underflow"); | |
| allocation_metrics.live_bytes -= size; | |
| } | |
| free(pointer); | |
| } | |
| static struct ck_malloc allocator = { | |
| .malloc = benchmark_malloc, | |
| .free = benchmark_free | |
| }; | |
| static unsigned long | |
| key_hash(const void *object, unsigned long seed) | |
| { | |
| const struct key *key = object; | |
| (void)seed; | |
| return key->hash; | |
| } | |
| static bool | |
| key_compare(const void *left, const void *right) | |
| { | |
| const struct key *a = left; | |
| const struct key *b = right; | |
| return a->id == b->id; | |
| } | |
| static unsigned long | |
| hash_for_id(uint64_t id) | |
| { | |
| /* Exercise clustered probes without constraining the total hash space. */ | |
| return (unsigned long)mix64(id ^ rhs_seed) & ~0x7UL; | |
| } | |
| static struct key * | |
| slot_key(struct key_slot *slot) | |
| { | |
| return &slot->key[slot->version]; | |
| } | |
| static struct key * | |
| slot_alternate_key(struct key_slot *slot) | |
| { | |
| return &slot->key[slot->version ^ 1U]; | |
| } | |
| static void | |
| pool_init(struct key_pool *pool, size_t capacity) | |
| { | |
| struct key_slot *slot; | |
| size_t i; | |
| memset(pool, 0, sizeof(*pool)); | |
| pool->slots = calloc(capacity, sizeof(*pool->slots)); | |
| pool->active = malloc(capacity * sizeof(*pool->active)); | |
| pool->inactive = malloc(capacity * sizeof(*pool->inactive)); | |
| if (pool->slots == NULL || pool->active == NULL || pool->inactive == NULL) | |
| benchmark_abort("failed to allocate key pool"); | |
| pool->capacity = capacity; | |
| pool->inactive_count = capacity; | |
| for (i = 0; i < capacity; i++) { | |
| slot = &pool->slots[i]; | |
| slot->key[0].id = slot->key[1].id = (uint64_t)i + 1; | |
| slot->key[0].hash = slot->key[1].hash = | |
| hash_for_id((uint64_t)i + 1); | |
| slot->index = i; | |
| pool->inactive[i] = slot; | |
| } | |
| } | |
| static void | |
| pool_destroy(struct key_pool *pool) | |
| { | |
| free(pool->inactive); | |
| free(pool->active); | |
| free(pool->slots); | |
| } | |
| static struct key_slot * | |
| pool_select(struct key_pool *pool, uint64_t *random, bool *exists) | |
| { | |
| *exists = pool->active_count != 0 && | |
| (pool->inactive_count == 0 || (random_next(random) & 1) != 0); | |
| if (*exists) { | |
| return pool->active[random_next(random) % pool->active_count]; | |
| } | |
| return pool->inactive[random_next(random) % pool->inactive_count]; | |
| } | |
| static void | |
| pool_activate(struct key_pool *pool, struct key_slot *slot, | |
| unsigned int version) | |
| { | |
| struct key_slot *moved; | |
| if (slot->active) | |
| benchmark_abort("activated an active key slot"); | |
| pool->inactive_count--; | |
| moved = pool->inactive[pool->inactive_count]; | |
| pool->inactive[slot->index] = moved; | |
| moved->index = slot->index; | |
| slot->active = true; | |
| slot->version = version; | |
| slot->index = pool->active_count; | |
| pool->active[pool->active_count++] = slot; | |
| } | |
| static void | |
| pool_deactivate(struct key_pool *pool, struct key_slot *slot) | |
| { | |
| struct key_slot *moved; | |
| if (slot->active == false) | |
| benchmark_abort("deactivated an inactive key slot"); | |
| pool->active_count--; | |
| moved = pool->active[pool->active_count]; | |
| pool->active[slot->index] = moved; | |
| moved->index = slot->index; | |
| slot->active = false; | |
| slot->index = pool->inactive_count; | |
| pool->inactive[pool->inactive_count++] = slot; | |
| } | |
| static struct key_slot * | |
| pool_slot_for_key(struct key_pool *pool, const struct key *key) | |
| { | |
| struct key_slot *slot; | |
| if (key->id == 0 || key->id > pool->capacity) | |
| benchmark_abort("ck_rhs returned a key outside the key pool"); | |
| slot = &pool->slots[key->id - 1]; | |
| if (slot->active == false || slot_key(slot) != key) | |
| benchmark_abort("ck_rhs returned a stale key pointer"); | |
| return slot; | |
| } | |
| static uint64_t | |
| measurement_start(struct measurement *measurement) | |
| { | |
| if (measurement_enabled == false) | |
| return 0; | |
| measurement->calls++; | |
| return benchmark_timestamp(); | |
| } | |
| static void | |
| measurement_stop(struct measurement *measurement, uint64_t start) | |
| { | |
| if (measurement_enabled) | |
| measurement->cycles += benchmark_timestamp() - start; | |
| } | |
| static bool | |
| timed_rhs_init(ck_rhs_t *rhs, unsigned long capacity) | |
| { | |
| uint64_t start; | |
| bool result; | |
| start = measurement_start(&api_measurements[API_INIT]); | |
| result = ck_rhs_init(rhs, CK_RHS_MODE_SPMC | CK_RHS_MODE_OBJECT, | |
| key_hash, key_compare, &allocator, capacity, (unsigned long)rhs_seed); | |
| measurement_stop(&api_measurements[API_INIT], start); | |
| return result; | |
| } | |
| static void | |
| timed_rhs_destroy(ck_rhs_t *rhs) | |
| { | |
| uint64_t start; | |
| start = measurement_start(&api_measurements[API_DESTROY]); | |
| ck_rhs_destroy(rhs); | |
| measurement_stop(&api_measurements[API_DESTROY], start); | |
| } | |
| static bool | |
| timed_rhs_put(ck_rhs_t *rhs, unsigned long hash, const void *key) | |
| { | |
| uint64_t start; | |
| bool result; | |
| start = measurement_start(&api_measurements[API_PUT]); | |
| result = ck_rhs_put(rhs, hash, key); | |
| measurement_stop(&api_measurements[API_PUT], start); | |
| return result; | |
| } | |
| static bool | |
| timed_rhs_set(ck_rhs_t *rhs, unsigned long hash, const void *key, | |
| void **previous) | |
| { | |
| uint64_t start; | |
| bool result; | |
| start = measurement_start(&api_measurements[API_SET]); | |
| result = ck_rhs_set(rhs, hash, key, previous); | |
| measurement_stop(&api_measurements[API_SET], start); | |
| return result; | |
| } | |
| static bool | |
| timed_rhs_fas(ck_rhs_t *rhs, unsigned long hash, const void *key, | |
| void **previous) | |
| { | |
| uint64_t start; | |
| bool result; | |
| start = measurement_start(&api_measurements[API_FAS]); | |
| result = ck_rhs_fas(rhs, hash, key, previous); | |
| measurement_stop(&api_measurements[API_FAS], start); | |
| return result; | |
| } | |
| static void * | |
| timed_rhs_get(ck_rhs_t *rhs, unsigned long hash, const void *key) | |
| { | |
| uint64_t start; | |
| void *result; | |
| start = measurement_start(&api_measurements[API_GET]); | |
| result = ck_rhs_get(rhs, hash, key); | |
| measurement_stop(&api_measurements[API_GET], start); | |
| return result; | |
| } | |
| static void * | |
| timed_rhs_remove(ck_rhs_t *rhs, unsigned long hash, const void *key) | |
| { | |
| uint64_t start; | |
| void *result; | |
| start = measurement_start(&api_measurements[API_REMOVE]); | |
| result = ck_rhs_remove(rhs, hash, key); | |
| measurement_stop(&api_measurements[API_REMOVE], start); | |
| return result; | |
| } | |
| static unsigned long | |
| timed_rhs_count(ck_rhs_t *rhs) | |
| { | |
| uint64_t start; | |
| unsigned long result; | |
| start = measurement_start(&api_measurements[API_COUNT]); | |
| result = ck_rhs_count(rhs); | |
| measurement_stop(&api_measurements[API_COUNT], start); | |
| return result; | |
| } | |
| static void | |
| timed_rhs_iterator_init(ck_rhs_iterator_t *iterator) | |
| { | |
| uint64_t start; | |
| start = measurement_start(&api_measurements[API_ITERATOR_INIT]); | |
| ck_rhs_iterator_init(iterator); | |
| measurement_stop(&api_measurements[API_ITERATOR_INIT], start); | |
| } | |
| static bool | |
| timed_rhs_next(ck_rhs_t *rhs, ck_rhs_iterator_t *iterator, void **key) | |
| { | |
| uint64_t start; | |
| bool result; | |
| start = measurement_start(&api_measurements[API_NEXT]); | |
| result = ck_rhs_next(rhs, iterator, key); | |
| measurement_stop(&api_measurements[API_NEXT], start); | |
| return result; | |
| } | |
| static void | |
| validate_count(ck_rhs_t *rhs, const struct key_pool *pool) | |
| { | |
| if (timed_rhs_count(rhs) != pool->active_count) | |
| benchmark_abort("ck_rhs_count did not match the key pool"); | |
| } | |
| static void | |
| validate_all(ck_rhs_t *rhs, struct key_pool *pool) | |
| { | |
| ck_rhs_iterator_t iterator; | |
| struct key_slot *slot; | |
| struct key *key; | |
| uint64_t start; | |
| size_t iterated, i; | |
| start = measurement_start(&meta_measurements[META_FULL_VALIDATION]); | |
| validate_count(rhs, pool); | |
| seen_epoch++; | |
| if (seen_epoch == 0) | |
| benchmark_abort("iteration epoch overflow"); | |
| iterated = 0; | |
| timed_rhs_iterator_init(&iterator); | |
| while (timed_rhs_next(rhs, &iterator, (void **)&key)) { | |
| slot = pool_slot_for_key(pool, key); | |
| if (slot->seen_epoch == seen_epoch) | |
| benchmark_abort("iterator returned a key more than once"); | |
| slot->seen_epoch = seen_epoch; | |
| iterated++; | |
| } | |
| if (iterated != pool->active_count) | |
| benchmark_abort("iterator did not return every active key"); | |
| for (i = 0; i < pool->active_count; i++) { | |
| slot = pool->active[i]; | |
| if (slot->seen_epoch != seen_epoch) | |
| benchmark_abort("iterator missed an active key"); | |
| key = slot_key(slot); | |
| if (timed_rhs_get(rhs, key->hash, key) != key) | |
| benchmark_abort("ck_rhs_get missed an active key"); | |
| } | |
| measurement_stop(&meta_measurements[META_FULL_VALIDATION], start); | |
| } | |
| static void | |
| operation_put(ck_rhs_t *rhs, struct key_pool *pool, uint64_t *random) | |
| { | |
| struct key_slot *slot; | |
| struct key *candidate, *expected; | |
| uint64_t start; | |
| unsigned int version; | |
| bool exists, result; | |
| start = measurement_start(&random_measurements[RANDOM_PUT]); | |
| slot = pool_select(pool, random, &exists); | |
| expected = exists ? slot_key(slot) : NULL; | |
| version = slot->version ^ 1U; | |
| candidate = &slot->key[version]; | |
| result = timed_rhs_put(rhs, candidate->hash, candidate); | |
| if (result == exists) | |
| benchmark_abort("ck_rhs_put returned an unexpected result"); | |
| if (result) | |
| pool_activate(pool, slot, version); | |
| if (timed_rhs_get(rhs, candidate->hash, candidate) != | |
| (result ? candidate : expected)) { | |
| benchmark_abort("ck_rhs_put left an unexpected value"); | |
| } | |
| measurement_stop(&random_measurements[RANDOM_PUT], start); | |
| } | |
| static void | |
| operation_set(ck_rhs_t *rhs, struct key_pool *pool, uint64_t *random) | |
| { | |
| struct key_slot *slot; | |
| struct key *candidate, *expected; | |
| void *previous; | |
| uint64_t start; | |
| unsigned int version; | |
| bool exists; | |
| start = measurement_start(&random_measurements[RANDOM_SET]); | |
| slot = pool_select(pool, random, &exists); | |
| expected = exists ? slot_key(slot) : NULL; | |
| version = slot->version ^ 1U; | |
| candidate = &slot->key[version]; | |
| previous = (void *)(uintptr_t)1; | |
| if (timed_rhs_set(rhs, candidate->hash, candidate, &previous) == false) | |
| benchmark_abort("ck_rhs_set failed"); | |
| if (previous != expected) | |
| benchmark_abort("ck_rhs_set returned an unexpected previous value"); | |
| if (exists) | |
| slot->version = version; | |
| else | |
| pool_activate(pool, slot, version); | |
| if (timed_rhs_get(rhs, candidate->hash, candidate) != candidate) | |
| benchmark_abort("ck_rhs_set left an unexpected value"); | |
| measurement_stop(&random_measurements[RANDOM_SET], start); | |
| } | |
| static void | |
| operation_fas(ck_rhs_t *rhs, struct key_pool *pool, uint64_t *random) | |
| { | |
| struct key_slot *slot; | |
| struct key *candidate, *expected; | |
| void *previous; | |
| uint64_t start; | |
| unsigned int version; | |
| bool exists, result; | |
| start = measurement_start(&random_measurements[RANDOM_FAS]); | |
| slot = pool_select(pool, random, &exists); | |
| expected = exists ? slot_key(slot) : NULL; | |
| version = slot->version ^ 1U; | |
| candidate = &slot->key[version]; | |
| previous = (void *)(uintptr_t)1; | |
| result = timed_rhs_fas(rhs, candidate->hash, candidate, &previous); | |
| if (result != exists) | |
| benchmark_abort("ck_rhs_fas returned an unexpected result"); | |
| if (result) { | |
| if (previous != expected) | |
| benchmark_abort("ck_rhs_fas returned the wrong previous value"); | |
| slot->version = version; | |
| } else if (previous != NULL) { | |
| benchmark_abort("failed ck_rhs_fas returned a previous value"); | |
| } | |
| if (timed_rhs_get(rhs, candidate->hash, candidate) != | |
| (result ? candidate : expected)) { | |
| benchmark_abort("ck_rhs_fas left an unexpected value"); | |
| } | |
| measurement_stop(&random_measurements[RANDOM_FAS], start); | |
| } | |
| static void | |
| operation_get(ck_rhs_t *rhs, struct key_pool *pool, uint64_t *random) | |
| { | |
| struct key_slot *slot; | |
| struct key *probe, *expected; | |
| uint64_t start; | |
| bool exists; | |
| start = measurement_start(&random_measurements[RANDOM_GET]); | |
| slot = pool_select(pool, random, &exists); | |
| probe = slot_alternate_key(slot); | |
| expected = exists ? slot_key(slot) : NULL; | |
| if (timed_rhs_get(rhs, probe->hash, probe) != expected) | |
| benchmark_abort("ck_rhs_get returned an unexpected value"); | |
| measurement_stop(&random_measurements[RANDOM_GET], start); | |
| } | |
| static void | |
| operation_remove(ck_rhs_t *rhs, struct key_pool *pool, uint64_t *random) | |
| { | |
| struct key_slot *slot; | |
| struct key *probe, *expected; | |
| void *removed; | |
| uint64_t start; | |
| bool exists; | |
| start = measurement_start(&random_measurements[RANDOM_REMOVE]); | |
| slot = pool_select(pool, random, &exists); | |
| probe = slot_alternate_key(slot); | |
| expected = exists ? slot_key(slot) : NULL; | |
| removed = timed_rhs_remove(rhs, probe->hash, probe); | |
| if (removed != expected) | |
| benchmark_abort("ck_rhs_remove returned an unexpected value"); | |
| if (removed != NULL) { | |
| pool_deactivate(pool, slot); | |
| if (timed_rhs_get(rhs, probe->hash, probe) != NULL) | |
| benchmark_abort("ck_rhs_remove left a reachable value"); | |
| } | |
| measurement_stop(&random_measurements[RANDOM_REMOVE], start); | |
| } | |
| static void | |
| operation_iteration(ck_rhs_t *rhs, struct key_pool *pool) | |
| { | |
| uint64_t start; | |
| start = measurement_start(&meta_measurements[META_ITERATION]); | |
| validate_all(rhs, pool); | |
| measurement_stop(&meta_measurements[META_ITERATION], start); | |
| } | |
| static void | |
| operation_batch_delete(ck_rhs_t *rhs, struct key_pool *pool) | |
| { | |
| struct key *keys[BATCH_SIZE], *key; | |
| struct key_slot *slot; | |
| ck_rhs_iterator_t iterator; | |
| uint64_t start; | |
| unsigned long before; | |
| size_t position, i; | |
| start = measurement_start(&meta_measurements[META_BATCH_DELETE]); | |
| while (timed_rhs_count(rhs) > 0) { | |
| validate_count(rhs, pool); | |
| before = timed_rhs_count(rhs); | |
| timed_rhs_iterator_init(&iterator); | |
| position = 0; | |
| while (position < BATCH_SIZE && | |
| timed_rhs_next(rhs, &iterator, (void **)&keys[position])) { | |
| pool_slot_for_key(pool, keys[position]); | |
| position++; | |
| } | |
| if (position == 0) | |
| benchmark_abort("batch deletion made no progress"); | |
| for (i = 0; i < position; i++) { | |
| key = keys[i]; | |
| slot = pool_slot_for_key(pool, key); | |
| if (timed_rhs_remove(rhs, key->hash, key) != key) | |
| benchmark_abort("batch deletion missed a key"); | |
| pool_deactivate(pool, slot); | |
| } | |
| if (timed_rhs_count(rhs) != before - position) | |
| benchmark_abort("batch deletion count mismatch"); | |
| } | |
| if (pool->active_count != 0) | |
| benchmark_abort("batch deletion left active keys"); | |
| measurement_stop(&meta_measurements[META_BATCH_DELETE], start); | |
| } | |
| static uint64_t | |
| parse_u64(const char *option, const char *value) | |
| { | |
| char *end; | |
| uintmax_t parsed; | |
| errno = 0; | |
| end = NULL; | |
| parsed = strtoumax(value, &end, 0); | |
| if (errno != 0 || value[0] == '-' || end == value || *end != '\0' || | |
| parsed > UINT64_MAX) { | |
| fprintf(stderr, "Invalid value for %s: %s\n", option, value); | |
| exit(EXIT_FAILURE); | |
| } | |
| return (uint64_t)parsed; | |
| } | |
| static void | |
| usage(FILE *stream, const char *program_name) | |
| { | |
| fprintf(stream, | |
| "Usage: %s [--ops NUMBER] [--seed NUMBER]\n" | |
| "\n" | |
| " --ops NUMBER Randomized operations (default: %" PRIu64 ")\n" | |
| " --seed NUMBER PRNG and ck_rhs seed (default: 0)\n", | |
| program_name, DEFAULT_OPERATIONS); | |
| } | |
| static void | |
| print_measurements(const char *heading, const char *const *names, | |
| struct measurement *measurements, size_t count) | |
| { | |
| size_t i; | |
| double per_call; | |
| printf("\n%s:\n", heading); | |
| printf(" %-22s %12s %18s %14s\n", "operation", "calls", | |
| "total cycles", "cycles/call"); | |
| for (i = 0; i < count; i++) { | |
| per_call = measurements[i].calls == 0 ? 0.0 : | |
| (double)measurements[i].cycles / measurements[i].calls; | |
| printf(" %-22s %12" PRIu64 " %18" PRIu64 " %14.2f\n", | |
| names[i], measurements[i].calls, measurements[i].cycles, | |
| per_call); | |
| } | |
| } | |
| static void | |
| run_randomized_workload(ck_rhs_t *rhs, struct key_pool *pool, | |
| uint64_t operations, uint64_t seed) | |
| { | |
| uint64_t current, random, selector; | |
| random = seed; | |
| for (current = 0; current < operations; current++) { | |
| selector = random_next(&random); | |
| if ((selector & UINT64_C(0x1ffff)) == 0) { | |
| operation_batch_delete(rhs, pool); | |
| } else if ((selector & UINT64_C(0x7ff)) == 1) { | |
| operation_iteration(rhs, pool); | |
| } else { | |
| selector = (selector >> 17) % 100; | |
| if (selector < 25) | |
| operation_put(rhs, pool, &random); | |
| else if (selector < 45) | |
| operation_set(rhs, pool, &random); | |
| else if (selector < 60) | |
| operation_fas(rhs, pool, &random); | |
| else if (selector < 80) | |
| operation_get(rhs, pool, &random); | |
| else | |
| operation_remove(rhs, pool, &random); | |
| } | |
| validate_count(rhs, pool); | |
| } | |
| } | |
| int | |
| main(int argc, char **argv) | |
| { | |
| struct key_pool pool; | |
| struct ck_rhs_stat stat; | |
| ck_rhs_t rhs; | |
| uint64_t operations, seed, workload_start; | |
| uint64_t workload_cycles; | |
| size_t final_keys; | |
| size_t pool_capacity; | |
| int i; | |
| operations = DEFAULT_OPERATIONS; | |
| seed = 0; | |
| for (i = 1; i < argc; i++) { | |
| if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { | |
| usage(stdout, argv[0]); | |
| return EXIT_SUCCESS; | |
| } else if (strcmp(argv[i], "--ops") == 0) { | |
| if (++i == argc) { | |
| usage(stderr, argv[0]); | |
| return EXIT_FAILURE; | |
| } | |
| operations = parse_u64("--ops", argv[i]); | |
| } else if (strcmp(argv[i], "--seed") == 0) { | |
| if (++i == argc) { | |
| usage(stderr, argv[0]); | |
| return EXIT_FAILURE; | |
| } | |
| seed = parse_u64("--seed", argv[i]); | |
| } else { | |
| fprintf(stderr, "Unknown option: %s\n", argv[i]); | |
| usage(stderr, argv[0]); | |
| return EXIT_FAILURE; | |
| } | |
| } | |
| if (operations > SIZE_MAX - 1 || | |
| operations + 1 > SIZE_MAX / sizeof(*pool.slots) || | |
| operations + 1 > SIZE_MAX / sizeof(*pool.active)) { | |
| benchmark_abort("--ops is too large for the key pool"); | |
| } | |
| pool_capacity = (size_t)operations + 1; | |
| rhs_seed = mix64(seed ^ UINT64_C(0xa0761d6478bd642f)); | |
| /* | |
| * The instrumented pass provides operation and allocator breakdowns. | |
| * A second, uninstrumented pass provides an end-to-end workload number | |
| * that is not dominated by per-call timestamp overhead. | |
| */ | |
| measurement_enabled = true; | |
| allocation_tracking_enabled = true; | |
| pool_init(&pool, pool_capacity); | |
| if (timed_rhs_init(&rhs, INITIAL_CAPACITY) == false) | |
| benchmark_abort("ck_rhs_init failed"); | |
| run_randomized_workload(&rhs, &pool, operations, seed); | |
| validate_all(&rhs, &pool); | |
| ck_rhs_stat(&rhs, &stat); | |
| final_keys = pool.active_count; | |
| timed_rhs_destroy(&rhs); | |
| pool_destroy(&pool); | |
| measurement_enabled = false; | |
| allocation_tracking_enabled = false; | |
| pool_init(&pool, pool_capacity); | |
| if (timed_rhs_init(&rhs, INITIAL_CAPACITY) == false) | |
| benchmark_abort("ck_rhs_init failed during uninstrumented pass"); | |
| workload_start = benchmark_timestamp(); | |
| run_randomized_workload(&rhs, &pool, operations, seed); | |
| workload_cycles = benchmark_timestamp() - workload_start; | |
| validate_all(&rhs, &pool); | |
| timed_rhs_destroy(&rhs); | |
| pool_destroy(&pool); | |
| printf("ck_rhs mixed benchmark\n"); | |
| printf("seed=%" PRIu64 " ops=%" PRIu64 " initial_capacity=%u " | |
| "final_keys=%zu probe_maximum=%u\n", seed, operations, | |
| INITIAL_CAPACITY, final_keys, stat.probe_maximum); | |
| printf("mixed_workload_cycles=%" PRIu64 " cycles/op=%.2f\n", | |
| workload_cycles, operations == 0 ? 0.0 : | |
| (double)workload_cycles / operations); | |
| print_measurements("Randomized operations", random_names, | |
| random_measurements, RANDOM_OPERATIONS); | |
| print_measurements("Meta operations", meta_names, meta_measurements, | |
| META_OPERATIONS); | |
| print_measurements("ck_rhs API calls", api_names, api_measurements, | |
| API_OPERATIONS); | |
| printf("\nAllocator:\n"); | |
| printf(" requests=%" PRIu64 " requested_bytes=%" PRIu64 "\n", | |
| allocation_metrics.requests, allocation_metrics.requested_bytes); | |
| printf(" frees=%" PRIu64 " freed_bytes=%" PRIu64 "\n", | |
| allocation_metrics.frees, allocation_metrics.freed_bytes); | |
| printf(" peak_live_bytes=%" PRIu64 " final_live_bytes=%" PRIu64 "\n", | |
| allocation_metrics.peak_live_bytes, allocation_metrics.live_bytes); | |
| return EXIT_SUCCESS; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment