Files
hakmem/benchmarks/redis/workload_bench.c

299 lines
8.3 KiB
C
Raw Normal View History

// Redis-style workload benchmark
// Tests small string allocations (16B-1KB) typical in Redis
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#define ITERATIONS 1000000
#define MAX_SIZE 1024
#define MIN_SIZE 16
typedef struct {
size_t size;
char data[MAX_SIZE];
} RedisString;
typedef struct {
RedisString* strings;
int count;
} StringPool;
static inline double now_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (ts.tv_sec * 1e9 + ts.tv_nsec);
}
// Redis-like string operations (alloc/free)
void* redis_malloc(size_t size) {
return malloc(size);
}
void redis_free(void* ptr) {
free(ptr);
}
static void* redis_realloc(void* ptr, size_t size) {
return realloc(ptr, size);
}
// Thread-local string pool
__thread StringPool thread_pool;
void pool_init() {
thread_pool.count = 0;
thread_pool.strings = NULL;
}
void pool_cleanup() {
for (int i = 0; i < thread_pool.count; i++) {
redis_free(thread_pool.strings[i].data);
}
free(thread_pool.strings);
thread_pool.count = 0;
}
char* pool_alloc(size_t size) {
if (thread_pool.count > 0) {
thread_pool.count--;
char* ptr = thread_pool.strings[thread_pool.count].data;
if (ptr) {
strcpy(ptr, "");
return ptr;
}
}
return (char*)malloc(size);
}
void pool_free(char* ptr, size_t size) {
if (thread_pool.strings &&
ptr >= thread_pool.strings[0].data &&
ptr <= thread_pool.strings[thread_pool.count-1].data) {
return; // Let pool cleanup handle it
}
free(ptr);
}
void* pool_strdup(const char* s) {
size_t len = strlen(s);
char* ptr = pool_alloc(len + 1);
if (ptr) {
strcpy(ptr, s);
return ptr;
}
return NULL;
}
// Workload simulation
typedef struct {
size_t min_size;
size_t max_size;
int num_strings;
int ops_per_cycle;
int cycles;
double* results;
} WorkloadConfig;
typedef struct {
pthread_t thread_id;
WorkloadConfig config;
double result;
} ThreadArg;
void* worker_thread(void* arg) {
ThreadArg* args = (ThreadArg*)arg;
WorkloadConfig* config = &args->config;
double total_time = 0.0;
pool_init();
for (int cycle = 0; cycle < config->cycles; cycle++) {
double start = now_ns();
// Allocate phase
for (int i = 0; i < config->ops_per_cycle; i++) {
size_t size = config->min_size +
(rand() % (config->max_size - config->min_size));
char* ptr = (char*)redis_malloc(size);
if (ptr) {
snprintf(ptr, size, "key%d", i);
}
}
// Random access phase
for (int i = 0; i < config->ops_per_cycle; i++) {
int idx = rand() % config->num_strings;
if (idx < thread_pool.count && thread_pool.strings[idx].data) {
pool_free(thread_pool.strings[idx].data,
strlen(thread_pool.strings[idx].data));
}
}
// Free phase (reverse order for LIFO)
for (int i = config->ops_per_cycle - 1; i >= 0; i--) {
size_t idx = rand() % config->num_strings;
if (idx < thread_pool.count && thread_pool.strings[idx].data) {
pool_free(thread_pool.strings[idx].data,
strlen(thread_pool.strings[idx].data));
}
}
double end = now_ns();
total_time += (end - start);
args->result = (config->ops_per_cycle * 2ULL) / total_time * 1000.0; // M ops/sec
}
pool_cleanup();
args->result /= config->cycles;
pthread_exit(0);
}
// Redis-style workload patterns
typedef enum {
REDIS_SET_ADD = 0,
REDIS_SET_GET = 1,
REDIS_LPUSH = 2,
REDIS_LPOP = 3,
RANDOM_ACCESS = 4
} RedisPattern;
const char* pattern_names[] = {
"SET", "GET", "LPUSH", "LPOP", "RANDOM"
};
RedisPattern get_redis_pattern(void) {
// 70% GET, 20% SET, 5% LPUSH/LPOP, 5% random
int r = rand() % 100;
if (r < 70) return REDIS_GET;
else if (r < 90) return REDIS_SET;
else if (r < 95) return REDIS_LPUSH;
else return REDIS_LPOP;
else return RANDOM_ACCESS;
}
void* redis_style_alloc(void* ptr, size_t size, RedisPattern pattern, ThreadArg* args) {
size_t* pool_start = &args->config.min_size;
size_t* pool_end = &args->config.max_size;
switch (pattern) {
case REDIS_SET_ADD:
return pool_alloc(size);
case REDIS_GET:
if (*pool_start <= *pool_end && args->config.num_strings > 0) {
args->config.num_strings--;
return pool_strdup("value");
}
return redis_malloc(size);
case REDIS_LPUSH:
if (*pool_start <= *pool_end && args->config.num_strings > 0) {
args->config.num_strings++;
return pool_strdup("item");
}
return redis_malloc(size);
case REDIS_LPOP:
if (*pool_start <= *pool_end && args->config.num_strings > 0) {
args->config.num_strings--;
char* ptr = pool_strdup("item");
pool_free(ptr, strlen(ptr));
}
return redis_malloc(size);
case RANDOM_ACCESS:
return redis_malloc(size);
}
return NULL;
}
void* redis_style_free(void* ptr, size_t size, RedisPattern pattern, ThreadArg* args) {
if (!ptr) return;
switch (pattern) {
case REDIS_SET_ADD:
redis_free(ptr, size);
break;
case REDIS_GET:
if (ptr[0] == 'v') {
pool_free(ptr, size);
} else {
redis_free(ptr);
}
break;
case REDIS_LPUSH:
redis_free(ptr, size);
break;
case REDIS_LPOP:
redis_free(ptr, size);
break;
case RANDOM_ACCESS:
redis_free(ptr, size);
break;
}
}
void run_redis_benchmark(const char* name, RedisPattern pattern, int threads, int cycles, int ops, size_t min_size, size_t max_size) {
printf("=== %s Benchmark ===\n", name);
printf("Pattern: %s\n", pattern_names[pattern]);
printf("Threads: %d\n", threads);
printf("Cycles: %d\n", cycles);
printf("Ops per cycle: %d\n", ops);
printf("Size range: %zu-%zu bytes\n", min_size, max_size);
printf("=====================================\n");
pthread_t* threads = malloc(sizeof(pthread_t) * threads);
ThreadArg* args = malloc(sizeof(ThreadArg) * threads);
double total = 0.0;
// Initialize thread pools
for (int i = 0; i < threads; i++) {
args[i].config.min_size = min_size;
args[i].config.max_size = max_size;
args[i].config.num_strings = 100;
args[i].config.ops_per_cycle = ops;
args[i].config.cycles = cycles;
pthread_create(&threads[i], NULL, worker_thread, &args[i]);
}
// Wait for completion
for (int i = 0; i < threads; i++) {
pthread_join(threads[i], NULL);
total += args[i].result;
}
printf("Average throughput: %.2f M ops/sec\n", total / threads);
printf("=====================================\n\n");
free(threads);
free(args);
}
int main(int argc, char** argv) {
srand(time(NULL));
// Default parameters
int threads = 4;
int cycles = 1000;
int ops = 1000;
size_t min_size = 16;
size_t max_size = 1024;
if (argc >= 2) threads = atoi(argv[1]);
if (argc >= 3) cycles = atoi(argv[2]);
if (argc >= 4) ops = atoi(argv[3]);
if (argc >= 5) min_size = (size_t)atoi(argv[4]);
if (argc >= 6) max_size = (size_t)atoi(argv[5]);
// Test different Redis patterns
run_redis_benchmark("Redis SET_ADD", REDIS_SET_ADD, threads, cycles, ops, min_size, max_size);
run_redis_benchmark("Redis GET", REDIS_GET, threads, cycles, ops, min_size, max_size);
run_redis_benchmark("Redis LPUSH", REDIS_LPUSH, threads, cycles, ops, min_size, max_size);
run_redis_benchmark("Redis LPOP", REDIS_LPOP, threads, cycles, ops, min_size, max_size);
run_redis_benchmark("Random Access", RANDOM_ACCESS, threads, cycles, ops, min_size, max_size);
return 0;
}