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>
299 lines
8.3 KiB
C
299 lines
8.3 KiB
C
// 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;
|
|
}
|