Debug Counters Implementation - Clean History
Major Features: - Debug counter infrastructure for Refill Stage tracking - Free Pipeline counters (ss_local, ss_remote, tls_sll) - Diagnostic counters for early return analysis - Unified larson.sh benchmark runner with profiles - Phase 6-3 regression analysis documentation Bug Fixes: - Fix SuperSlab disabled by default (HAKMEM_TINY_USE_SUPERSLAB) - Fix profile variable naming consistency - Add .gitignore patterns for large files Performance: - Phase 6-3: 4.79 M ops/s (has OOM risk) - With SuperSlab: 3.13 M ops/s (+19% improvement) This is a clean repository without large log files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
298
benchmarks/redis/workload_bench.c
Normal file
298
benchmarks/redis/workload_bench.c
Normal file
@ -0,0 +1,298 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user