// Redis-style workload benchmark // Tests small string allocations (16B-1KB) typical in Redis #include #include #include #include #include #include #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; }