#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <math.h>
#include <stdatomic.h>
#include "exp_rs.h"
static atomic_size_t total_allocations = 0;
static atomic_size_t total_deallocations = 0;
static atomic_size_t current_bytes = 0;
static atomic_size_t peak_bytes = 0;
static atomic_size_t total_allocated_bytes = 0;
static atomic_bool tracking_enabled = false;
typedef struct {
size_t size;
size_t magic; } alloc_header_t;
#define ALLOC_MAGIC 0xDEADBEEF
#define HEADER_SIZE sizeof(alloc_header_t)
static void* (*original_malloc)(size_t) = NULL;
static void (*original_free)(void*) = NULL;
static bool tracking_initialized = false;
void init_memory_tracking() {
if (!tracking_initialized) {
original_malloc = malloc;
original_free = free;
tracking_initialized = true;
}
atomic_store(&total_allocations, 0);
atomic_store(&total_deallocations, 0);
atomic_store(¤t_bytes, 0);
atomic_store(&peak_bytes, 0);
atomic_store(&total_allocated_bytes, 0);
}
void enable_allocation_tracking() {
atomic_store(&tracking_enabled, true);
}
void disable_allocation_tracking() {
atomic_store(&tracking_enabled, false);
}
void* tracked_malloc(size_t size) {
void* ptr = original_malloc(size + HEADER_SIZE);
if (ptr && atomic_load(&tracking_enabled)) {
alloc_header_t* header = (alloc_header_t*)ptr;
header->size = size;
header->magic = ALLOC_MAGIC;
atomic_fetch_add(&total_allocations, 1);
atomic_fetch_add(&total_allocated_bytes, size);
size_t new_current = atomic_fetch_add(¤t_bytes, size) + size;
size_t current_peak = atomic_load(&peak_bytes);
while (new_current > current_peak) {
if (atomic_compare_exchange_weak(&peak_bytes, ¤t_peak, new_current)) {
break;
}
}
return (char*)ptr + HEADER_SIZE;
}
return ptr;
}
void tracked_free(void* ptr) {
if (!ptr) return;
if (atomic_load(&tracking_enabled)) {
alloc_header_t* header = (alloc_header_t*)((char*)ptr - HEADER_SIZE);
if (header->magic == ALLOC_MAGIC) {
size_t size = header->size;
header->magic = 0;
atomic_fetch_add(&total_deallocations, 1);
atomic_fetch_sub(¤t_bytes, size);
original_free(header);
} else {
original_free(ptr);
}
} else {
original_free(ptr);
}
}
typedef struct {
size_t total_allocs;
size_t total_deallocs;
size_t current_bytes;
size_t peak_bytes;
size_t total_allocated_bytes;
size_t leaked_allocs;
} memory_stats_t;
memory_stats_t get_memory_stats() {
memory_stats_t stats;
stats.total_allocs = atomic_load(&total_allocations);
stats.total_deallocs = atomic_load(&total_deallocations);
stats.current_bytes = atomic_load(¤t_bytes);
stats.peak_bytes = atomic_load(&peak_bytes);
stats.total_allocated_bytes = atomic_load(&total_allocated_bytes);
stats.leaked_allocs = stats.total_allocs - stats.total_deallocs;
return stats;
}
void print_memory_stats(const char* phase) {
memory_stats_t stats = get_memory_stats();
printf("Memory Stats [%s]:\n", phase);
printf(" Allocations: %zu\n", stats.total_allocs);
printf(" Deallocations: %zu\n", stats.total_deallocs);
printf(" Current bytes: %zu\n", stats.current_bytes);
printf(" Peak bytes: %zu (%.1f KB)\n", stats.peak_bytes, stats.peak_bytes / 1024.0);
printf(" Total allocated: %zu (%.1f KB)\n", stats.total_allocated_bytes, stats.total_allocated_bytes / 1024.0);
printf(" Leaked allocations: %zu\n", stats.leaked_allocs);
}
void reset_memory_stats() {
atomic_store(&total_allocations, 0);
atomic_store(&total_deallocations, 0);
atomic_store(¤t_bytes, 0);
atomic_store(&peak_bytes, 0);
atomic_store(&total_allocated_bytes, 0);
}
void* exp_rs_malloc(size_t size) {
printf("[DEBUG] exp_rs_malloc called with size: %zu\n", size);
fflush(stdout);
return tracked_malloc(size);
}
void exp_rs_free(void* ptr) {
printf("[DEBUG] exp_rs_free called with ptr: %p\n", ptr);
fflush(stdout);
tracked_free(ptr);
}
void test_custom_allocator_integration() {
printf("=== Test Custom Allocator Integration ===\n");
init_memory_tracking();
reset_memory_stats();
enable_allocation_tracking();
memory_stats_t start_stats = get_memory_stats();
printf("Starting stats: %zu allocs, %zu bytes\n",
start_stats.total_allocs, start_stats.total_allocated_bytes);
printf("Testing direct exp_rs_malloc call...\n");
void* test_ptr = exp_rs_malloc(1024);
memory_stats_t after_malloc = get_memory_stats();
printf("After direct malloc: %zu allocs, %zu bytes\n",
after_malloc.total_allocs, after_malloc.total_allocated_bytes);
if (after_malloc.total_allocs == start_stats.total_allocs) {
printf("❌ FAILED: exp_rs_malloc not tracked - custom allocator not working!\n");
printf("This means the Rust FFI is NOT using exp_rs_malloc/exp_rs_free\n");
exit(1); } else {
printf("✅ PASSED: exp_rs_malloc is being tracked\n");
}
printf("Testing exp_rs_free call...\n");
exp_rs_free(test_ptr);
memory_stats_t after_free = get_memory_stats();
printf("After free: %zu allocs, %zu deallocs, %zu current bytes\n",
after_free.total_allocs, after_free.total_deallocs, after_free.current_bytes);
printf("\nTesting FFI arena creation (should call exp_rs_malloc)...\n");
memory_stats_t before_arena = get_memory_stats();
ExprArena* arena = expr_arena_new(8192);
memory_stats_t after_arena = get_memory_stats();
printf("Arena creation stats: %zu allocs (+%zu), %zu bytes (+%zu)\n",
after_arena.total_allocs,
after_arena.total_allocs - before_arena.total_allocs,
after_arena.total_allocated_bytes,
after_arena.total_allocated_bytes - before_arena.total_allocated_bytes);
if (after_arena.total_allocs == before_arena.total_allocs) {
printf("❌ FAILED: Arena creation didn't trigger exp_rs_malloc!\n");
printf("This means Rust FFI is using a different allocator\n");
printf("Check if custom_cbindgen_alloc feature is actually enabled\n");
exit(1); } else {
printf("✅ PASSED: Arena creation uses custom allocator\n");
}
expr_arena_free(arena);
memory_stats_t after_cleanup = get_memory_stats();
printf("After arena free: %zu deallocs (+%zu)\n",
after_cleanup.total_deallocs,
after_cleanup.total_deallocs - after_arena.total_deallocs);
disable_allocation_tracking();
printf("\n");
}
static double get_time_us() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1e6 + ts.tv_nsec / 1e3;
}
Real native_sin(const Real* args, uintptr_t nargs) { (void)nargs; return sin(args[0]); }
Real native_cos(const Real* args, uintptr_t nargs) { (void)nargs; return cos(args[0]); }
Real native_tan(const Real* args, uintptr_t nargs) { (void)nargs; return tan(args[0]); }
Real native_sqrt(const Real* args, uintptr_t nargs) { (void)nargs; return sqrt(args[0]); }
Real native_exp(const Real* args, uintptr_t nargs) { (void)nargs; return exp(args[0]); }
Real native_log(const Real* args, uintptr_t nargs) { (void)nargs; return log(args[0]); }
Real native_log10(const Real* args, uintptr_t nargs) { (void)nargs; return log10(args[0]); }
Real native_pow(const Real* args, uintptr_t nargs) { (void)nargs; return pow(args[0], args[1]); }
Real native_atan2(const Real* args, uintptr_t nargs) { (void)nargs; return atan2(args[0], args[1]); }
Real native_abs(const Real* args, uintptr_t nargs) { (void)nargs; return fabs(args[0]); }
Real native_sign(const Real* args, uintptr_t nargs) {
(void)nargs;
return args[0] > 0.0 ? 1.0 : (args[0] < 0.0 ? -1.0 : 0.0);
}
Real native_min(const Real* args, uintptr_t nargs) { (void)nargs; return args[0] < args[1] ? args[0] : args[1]; }
Real native_max(const Real* args, uintptr_t nargs) { (void)nargs; return args[0] > args[1] ? args[0] : args[1]; }
Real native_fmod(const Real* args, uintptr_t nargs) { (void)nargs; return fmod(args[0], args[1]); }
void test_arena_lifecycle() {
printf("=== Test Arena Lifecycle ===\n");
ExprArena* arena = expr_arena_new(256 * 1024);
assert(arena != NULL);
printf("✓ Arena created successfully\n");
expr_arena_reset(arena);
printf("✓ Arena reset successfully\n");
expr_arena_free(arena);
printf("✓ Arena freed successfully\n\n");
}
void test_batch_builder_with_arena() {
printf("=== Test Batch Builder with Arena ===\n");
ExprArena* arena = expr_arena_new(256 * 1024);
assert(arena != NULL);
ExprBatch* builder = expr_batch_new(arena);
assert(builder != NULL);
printf("✓ Batch builder created with arena\n");
ExprResult expr1_res = expr_batch_add_expression(builder, "x + y");
assert(expr1_res.status == 0);
int32_t expr1_idx = expr1_res.index;
assert(expr1_idx == 0);
ExprResult expr2_res = expr_batch_add_expression(builder, "x * y");
assert(expr2_res.status == 0);
int32_t expr2_idx = expr2_res.index;
assert(expr2_idx == 1);
ExprResult expr3_res = expr_batch_add_expression(builder, "sqrt(x*x + y*y)");
assert(expr3_res.status == 0);
int32_t expr3_idx = expr3_res.index;
assert(expr3_idx == 2);
printf("✓ Added 3 expressions\n");
ExprResult x_res = expr_batch_add_variable(builder, "x", 3.0);
assert(x_res.status == 0);
int32_t x_idx = x_res.index;
assert(x_idx == 0);
ExprResult y_res = expr_batch_add_variable(builder, "y", 4.0);
assert(y_res.status == 0);
int32_t y_idx = y_res.index;
assert(y_idx == 1);
printf("✓ Added 2 parameters\n");
ExprContext* ctx = expr_context_new();
assert(ctx != NULL);
expr_context_add_function(ctx, "sqrt", 1, native_sqrt);
int32_t eval_result = expr_batch_evaluate(builder, ctx);
assert(eval_result == 0);
printf("✓ Evaluation successful\n");
Real result1 = expr_batch_get_result(builder, expr1_idx);
Real result2 = expr_batch_get_result(builder, expr2_idx);
Real result3 = expr_batch_get_result(builder, expr3_idx);
printf("Results: x+y=%.2f, x*y=%.2f, sqrt(x²+y²)=%.2f\n",
result1, result2, result3);
assert(result1 == 7.0); assert(result2 == 12.0); assert(result3 == 5.0); printf("✓ Results verified\n");
expr_context_free(ctx);
expr_batch_free(builder);
expr_arena_free(arena);
printf("✓ Cleanup successful\n\n");
}
void test_arena_reset_reuse() {
printf("=== Test Arena Reset and Reuse ===\n");
ExprArena* arena = expr_arena_new(128 * 1024);
ExprContext* ctx = expr_context_new();
ExprBatch* builder1 = expr_batch_new(arena);
ExprResult res = expr_batch_add_expression(builder1, "a + b + c");
assert(res.status == 0);
res = expr_batch_add_variable(builder1, "a", 1.0);
assert(res.status == 0);
res = expr_batch_add_variable(builder1, "b", 2.0);
assert(res.status == 0);
res = expr_batch_add_variable(builder1, "c", 3.0);
assert(res.status == 0);
expr_batch_evaluate(builder1, ctx);
Real result1 = expr_batch_get_result(builder1, 0);
assert(result1 == 6.0);
printf("✓ First evaluation: %.2f\n", result1);
expr_batch_free(builder1);
expr_arena_reset(arena);
printf("✓ Arena reset\n");
ExprBatch* builder2 = expr_batch_new(arena);
res = expr_batch_add_expression(builder2, "x * y * z");
assert(res.status == 0);
res = expr_batch_add_variable(builder2, "x", 2.0);
assert(res.status == 0);
res = expr_batch_add_variable(builder2, "y", 3.0);
assert(res.status == 0);
res = expr_batch_add_variable(builder2, "z", 4.0);
assert(res.status == 0);
expr_batch_evaluate(builder2, ctx);
Real result2 = expr_batch_get_result(builder2, 0);
assert(result2 == 24.0);
printf("✓ Second evaluation: %.2f\n", result2);
expr_batch_free(builder2);
expr_context_free(ctx);
expr_arena_free(arena);
printf("✓ Arena reuse successful\n\n");
}
void test_benchmark_expressions() {
printf("=== Test Benchmark Expressions (matching Rust benchmark) ===\n");
ExprArena* arena = expr_arena_new(512 * 1024);
ExprContext* ctx = expr_context_new();
expr_context_add_function(ctx, "sin", 1, native_sin);
expr_context_add_function(ctx, "cos", 1, native_cos);
expr_context_add_function(ctx, "tan", 1, native_tan);
expr_context_add_function(ctx, "sqrt", 1, native_sqrt);
expr_context_add_function(ctx, "exp", 1, native_exp);
expr_context_add_function(ctx, "log", 1, native_log);
expr_context_add_function(ctx, "log10", 1, native_log10);
expr_context_add_function(ctx, "pow", 2, native_pow);
expr_context_add_function(ctx, "atan2", 2, native_atan2);
expr_context_add_function(ctx, "abs", 1, native_abs);
expr_context_add_function(ctx, "sign", 1, native_sign);
expr_context_add_function(ctx, "min", 2, native_min);
expr_context_add_function(ctx, "max", 2, native_max);
expr_context_add_function(ctx, "fmod", 2, native_fmod);
ExprBatch* builder = expr_batch_new(arena);
const char* expressions[] = {
"a*sin(b*3.14159/180) + c*cos(d*3.14159/180) + sqrt(e*e + f*f)",
"exp(g/10) * log(h+1) + pow(i, 0.5) * j",
"((a > 5) && (b < 10)) * c + ((d >= e) || (f != g)) * h + min(i, j)",
"sqrt(pow(a-e, 2) + pow(b-f, 2)) + atan2(c-g, d-h) * (i+j)/2",
"abs(a-b) * sign(c-d) + max(e, f) * min(g, h) + fmod(i*j, 10)",
"(a+b+c)/3 * sin((d+e+f)*3.14159/6) + log10(g*h+1) - exp(-i*j/100)",
"a + b * c - d / (e + 0.001) + pow(f, g) * h - i + j"
};
for (int i = 0; i < 7; i++) {
ExprResult res = expr_batch_add_expression(builder, expressions[i]);
if (res.status != 0) {
printf("Failed to add expression %d: %s (error: %s)\n", i, expressions[i], res.error);
return;
}
}
printf("✓ Added 7 benchmark expressions\n");
const char* param_names[] = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"};
for (int i = 0; i < 10; i++) {
ExprResult res = expr_batch_add_variable(builder, param_names[i], (i + 1) * 1.5);
assert(res.status == 0);
}
printf("✓ Added 10 parameters (a-j)\n");
expr_batch_evaluate(builder, ctx);
printf("✓ Initial evaluation complete\n");
const int batch_sizes[] = {1, 10, 100, 1000};
for (int b = 0; b < 4; b++) {
int batch_size = batch_sizes[b];
printf("\nBatch size %d (simulating %dms at 1000Hz):\n", batch_size, batch_size);
const int iterations = 10000 / batch_size; double start = get_time_us();
for (int iter = 0; iter < iterations; iter++) {
for (int batch = 0; batch < batch_size; batch++) {
for (int p = 0; p < 10; p++) {
Real value = (p + 1) * 1.5 + (batch + 1) * 0.1;
expr_batch_set_variable(builder, p, value);
}
expr_batch_evaluate(builder, ctx);
}
}
double end = get_time_us();
double total_us = end - start;
double total_evals = iterations * batch_size * 7; double us_per_eval = total_us / total_evals;
double us_per_batch = total_us / (iterations * batch_size);
double batch_rate = 1e6 / us_per_batch;
printf(" Total evaluations: %.0f\n", total_evals);
printf(" Total time: %.2f ms\n", total_us / 1000.0);
printf(" Time per batch: %.3f µs\n", us_per_batch);
printf(" Time per expression: %.3f µs\n", us_per_eval);
printf(" Batch rate: %.0f Hz\n", batch_rate);
printf(" Target (1000 Hz): %s\n",
batch_rate >= 1000 ? "✓ ACHIEVED" : "✗ NOT ACHIEVED");
}
expr_batch_free(builder);
expr_context_free(ctx);
expr_arena_free(arena);
printf("\n");
}
void test_zero_allocations() {
printf("=== Test Zero Allocations During Evaluation ===\n");
init_memory_tracking();
disable_allocation_tracking();
ExprArena* arena = expr_arena_new(256 * 1024);
ExprContext* ctx = expr_context_new();
expr_context_add_function(ctx, "sin", 1, native_sin);
expr_context_add_function(ctx, "cos", 1, native_cos);
expr_context_add_function(ctx, "tan", 1, native_tan);
expr_context_add_function(ctx, "sqrt", 1, native_sqrt);
ExprBatch* builder = expr_batch_new(arena);
expr_batch_add_expression(builder,
"sin(x) * cos(y) + tan(z) * sqrt(x*x + y*y + z*z)");
expr_batch_add_variable(builder, "x", 0.0);
expr_batch_add_variable(builder, "y", 0.0);
expr_batch_add_variable(builder, "z", 0.0);
expr_batch_evaluate(builder, ctx);
printf("✓ Initial evaluation complete\n");
reset_memory_stats();
enable_allocation_tracking();
memory_stats_t before_stats = get_memory_stats();
printf("Starting evaluation loop with tracking enabled...\n");
const int iterations = 100000;
double start = get_time_us();
for (int i = 0; i < iterations; i++) {
Real x = (Real)(i % 100) / 100.0;
Real y = (Real)((i + 33) % 100) / 100.0;
Real z = (Real)((i + 66) % 100) / 100.0;
expr_batch_set_variable(builder, 0, x);
expr_batch_set_variable(builder, 1, y);
expr_batch_set_variable(builder, 2, z);
expr_batch_evaluate(builder, ctx);
}
double end = get_time_us();
memory_stats_t after_stats = get_memory_stats();
disable_allocation_tracking();
double total_us = end - start;
double us_per_eval = total_us / iterations;
double evals_per_sec = 1e6 / us_per_eval;
printf("✓ Completed %d evaluations\n", iterations);
printf(" Total time: %.2f ms\n", total_us / 1000.0);
printf(" Time per eval: %.3f µs\n", us_per_eval);
printf(" Evaluations/sec: %.0f\n", evals_per_sec);
printf(" Target (1000 Hz): %s\n",
evals_per_sec >= 1000 ? "✓ ACHIEVED" : "✗ NOT ACHIEVED");
size_t allocs_during_eval = after_stats.total_allocs - before_stats.total_allocs;
size_t bytes_during_eval = after_stats.total_allocated_bytes - before_stats.total_allocated_bytes;
printf("\n=== ALLOCATION ANALYSIS ===\n");
printf("Allocations during %d evaluations: %zu\n", iterations, allocs_during_eval);
printf("Bytes allocated during evaluations: %zu\n", bytes_during_eval);
printf("Zero allocation claim: %s\n",
allocs_during_eval == 0 ? "✓ VERIFIED" : "✗ FAILED");
if (allocs_during_eval > 0) {
printf("WARNING: Found %zu allocations during evaluation!\n", allocs_during_eval);
printf("Average bytes per evaluation: %.2f\n", (double)bytes_during_eval / iterations);
}
expr_batch_free(builder);
expr_context_free(ctx);
expr_arena_free(arena);
printf("\n");
}
void test_arena_size_estimation() {
printf("=== Test Arena Size Estimation ===\n");
const char* expressions[] = {
"x + y",
"sin(x) * cos(y)",
"sqrt(x*x + y*y)",
"x^3 + 2*x^2 + 3*x + 4",
"(x > 0 ? x : -x) * (y > 0 ? y : -y)"
};
size_t num_exprs = sizeof(expressions) / sizeof(expressions[0]);
size_t total_expr_length = 0;
for (int i = 0; i < num_exprs; i++) {
total_expr_length += strlen(expressions[i]);
}
size_t estimated_size = expr_estimate_arena_size(num_exprs, total_expr_length, 0, 1000);
printf("✓ Estimated arena size: %zu bytes (%.1f KB)\n",
estimated_size, estimated_size / 1024.0);
ExprArena* arena = expr_arena_new(estimated_size);
assert(arena != NULL);
printf("✓ Created arena with estimated size\n");
ExprBatch* builder = expr_batch_new(arena);
for (size_t i = 0; i < num_exprs; i++) {
ExprResult res = expr_batch_add_expression(builder, expressions[i]);
assert(res.status == 0);
assert(res.index == (int32_t)i);
}
printf("✓ Successfully added all expressions\n");
expr_batch_free(builder);
expr_arena_free(arena);
printf("\n");
}
void test_error_handling() {
printf("=== Test Error Handling ===\n");
ExprBatch* builder = expr_batch_new(NULL);
assert(builder == NULL);
printf("✓ NULL arena handled correctly\n");
ExprArena* arena = expr_arena_new(64 * 1024);
builder = expr_batch_new(arena);
ExprResult first_res = expr_batch_add_variable(builder, "x", 1.0);
assert(first_res.status == 0);
ExprResult dup_res = expr_batch_add_variable(builder, "x", 2.0);
assert(dup_res.status != 0); printf("✓ Duplicate parameter handled correctly\n");
expr_batch_free(builder);
expr_arena_free(arena);
printf("\n");
}
void test_method1_complete_recreation() {
printf("=== Test Method 1: Complete Arena Recreation ===\n");
init_memory_tracking();
disable_allocation_tracking();
ExprContext* ctx = expr_context_new();
expr_context_add_function(ctx, "sin", 1, native_sin);
expr_context_add_function(ctx, "cos", 1, native_cos);
expr_context_add_function(ctx, "sqrt", 1, native_sqrt);
printf("Long-lived context created\n");
const int num_batches = 10;
const int evals_per_batch = 1000;
for (int batch_num = 0; batch_num < num_batches; batch_num++) {
printf("\n--- Batch %d ---\n", batch_num + 1);
reset_memory_stats();
enable_allocation_tracking();
memory_stats_t start_stats = get_memory_stats();
ExprArena* arena = expr_arena_new(128 * 1024);
memory_stats_t after_arena = get_memory_stats();
ExprBatch* batch = expr_batch_new(arena);
memory_stats_t after_batch = get_memory_stats();
expr_batch_add_expression(batch, "sin(x) + cos(y)");
expr_batch_add_expression(batch, "sqrt(x*x + y*y)");
expr_batch_add_variable(batch, "x", 0.0);
expr_batch_add_variable(batch, "y", 0.0);
memory_stats_t after_setup = get_memory_stats();
expr_batch_evaluate(batch, ctx);
memory_stats_t after_initial = get_memory_stats();
for (int i = 0; i < evals_per_batch; i++) {
expr_batch_set_variable(batch, 0, (Real)i * 0.01);
expr_batch_set_variable(batch, 1, (Real)i * 0.02);
expr_batch_evaluate(batch, ctx);
}
memory_stats_t after_evals = get_memory_stats();
expr_batch_free(batch);
expr_arena_free(arena);
memory_stats_t after_cleanup = get_memory_stats();
disable_allocation_tracking();
printf("Arena allocation: %zu bytes\n",
after_arena.total_allocated_bytes - start_stats.total_allocated_bytes);
printf("Batch setup: %zu bytes\n",
after_setup.total_allocated_bytes - after_arena.total_allocated_bytes);
printf("Initial eval: %zu bytes\n",
after_initial.total_allocated_bytes - after_setup.total_allocated_bytes);
printf("%d evaluations: %zu bytes\n", evals_per_batch,
after_evals.total_allocated_bytes - after_initial.total_allocated_bytes);
printf("Current memory after cleanup: %zu bytes\n", after_cleanup.current_bytes);
printf("Peak memory this batch: %zu bytes (%.1f KB)\n",
after_cleanup.peak_bytes, after_cleanup.peak_bytes / 1024.0);
if (after_cleanup.current_bytes == 0) {
printf("✓ Complete memory reclamation verified\n");
} else {
printf("✗ Memory leak detected: %zu bytes\n", after_cleanup.current_bytes);
}
size_t eval_allocs = after_evals.total_allocs - after_initial.total_allocs;
if (eval_allocs == 0) {
printf("✓ Zero allocations during %d evaluations\n", evals_per_batch);
} else {
printf("✗ Found %zu allocations during evaluations\n", eval_allocs);
}
}
expr_context_free(ctx);
printf("\n✓ Method 1 complete - total memory freed after each batch\n\n");
}
void test_method2_arena_reset() {
printf("=== Test Method 2: Arena Reset Pattern ===\n");
init_memory_tracking();
disable_allocation_tracking();
ExprContext* ctx = expr_context_new();
expr_context_add_function(ctx, "sin", 1, native_sin);
expr_context_add_function(ctx, "cos", 1, native_cos);
expr_context_add_function(ctx, "sqrt", 1, native_sqrt);
ExprArena* arena = expr_arena_new(128 * 1024);
printf("Long-lived context and arena created\n");
const int num_batches = 10;
const int evals_per_batch = 1000;
for (int batch_num = 0; batch_num < num_batches; batch_num++) {
printf("\n--- Batch %d ---\n", batch_num + 1);
reset_memory_stats();
enable_allocation_tracking();
memory_stats_t start_stats = get_memory_stats();
expr_arena_reset(arena);
memory_stats_t after_reset = get_memory_stats();
ExprBatch* batch = expr_batch_new(arena);
memory_stats_t after_batch = get_memory_stats();
expr_batch_add_expression(batch, "sin(x) + cos(y)");
expr_batch_add_expression(batch, "sqrt(x*x + y*y)");
expr_batch_add_variable(batch, "x", 0.0);
expr_batch_add_variable(batch, "y", 0.0);
memory_stats_t after_setup = get_memory_stats();
expr_batch_evaluate(batch, ctx);
memory_stats_t after_initial = get_memory_stats();
for (int i = 0; i < evals_per_batch; i++) {
expr_batch_set_variable(batch, 0, (Real)i * 0.01);
expr_batch_set_variable(batch, 1, (Real)i * 0.02);
expr_batch_evaluate(batch, ctx);
}
memory_stats_t after_evals = get_memory_stats();
expr_batch_free(batch);
memory_stats_t after_cleanup = get_memory_stats();
disable_allocation_tracking();
printf("Arena reset: %zu bytes\n",
after_reset.total_allocated_bytes - start_stats.total_allocated_bytes);
printf("Batch creation: %zu bytes\n",
after_batch.total_allocated_bytes - after_reset.total_allocated_bytes);
printf("Expression setup: %zu bytes\n",
after_setup.total_allocated_bytes - after_batch.total_allocated_bytes);
printf("Initial eval: %zu bytes\n",
after_initial.total_allocated_bytes - after_setup.total_allocated_bytes);
printf("%d evaluations: %zu bytes\n", evals_per_batch,
after_evals.total_allocated_bytes - after_initial.total_allocated_bytes);
printf("Current memory after batch cleanup: %zu bytes\n", after_cleanup.current_bytes);
printf("Peak memory this batch: %zu bytes (%.1f KB)\n",
after_cleanup.peak_bytes, after_cleanup.peak_bytes / 1024.0);
size_t reset_allocs = after_reset.total_allocs - start_stats.total_allocs;
if (reset_allocs == 0) {
printf("✓ Arena reset with zero allocations\n");
} else {
printf("✗ Arena reset caused %zu allocations\n", reset_allocs);
}
size_t eval_allocs = after_evals.total_allocs - after_initial.total_allocs;
if (eval_allocs == 0) {
printf("✓ Zero allocations during %d evaluations\n", evals_per_batch);
} else {
printf("✗ Found %zu allocations during evaluations\n", eval_allocs);
}
}
expr_arena_free(arena);
expr_context_free(ctx);
printf("\n✓ Method 2 complete - arena memory kept allocated, reset between batches\n\n");
}
void test_memory_lifecycle_comparison() {
printf("=== Memory Lifecycle Comparison ===\n");
init_memory_tracking();
disable_allocation_tracking();
ExprContext* ctx1 = expr_context_new();
ExprContext* ctx2 = expr_context_new();
expr_context_add_function(ctx1, "sin", 1, native_sin);
expr_context_add_function(ctx1, "sqrt", 1, native_sqrt);
expr_context_add_function(ctx2, "sin", 1, native_sin);
expr_context_add_function(ctx2, "sqrt", 1, native_sqrt);
ExprArena* persistent_arena = expr_arena_new(128 * 1024);
const int test_batches = 5;
const int evals_per_batch = 1000;
printf("\nTesting %d batches with %d evaluations each...\n", test_batches, evals_per_batch);
printf("\n--- Method 1 Performance ---\n");
double method1_total_time = 0;
size_t method1_total_allocs = 0;
size_t method1_peak_memory = 0;
for (int batch = 0; batch < test_batches; batch++) {
reset_memory_stats();
enable_allocation_tracking();
double start_time = get_time_us();
ExprArena* arena = expr_arena_new(128 * 1024);
ExprBatch* batch_obj = expr_batch_new(arena);
expr_batch_add_expression(batch_obj, "sin(x) + sqrt(y)");
expr_batch_add_variable(batch_obj, "x", 0.0);
expr_batch_add_variable(batch_obj, "y", 1.0);
expr_batch_evaluate(batch_obj, ctx1);
for (int i = 0; i < evals_per_batch; i++) {
expr_batch_set_variable(batch_obj, 0, (Real)i * 0.01);
expr_batch_evaluate(batch_obj, ctx1);
}
expr_batch_free(batch_obj);
expr_arena_free(arena);
double end_time = get_time_us();
memory_stats_t stats = get_memory_stats();
method1_total_time += (end_time - start_time);
method1_total_allocs += stats.total_allocs;
if (stats.peak_bytes > method1_peak_memory) {
method1_peak_memory = stats.peak_bytes;
}
disable_allocation_tracking();
}
printf("\n--- Method 2 Performance ---\n");
double method2_total_time = 0;
size_t method2_total_allocs = 0;
size_t method2_peak_memory = 0;
for (int batch = 0; batch < test_batches; batch++) {
reset_memory_stats();
enable_allocation_tracking();
double start_time = get_time_us();
expr_arena_reset(persistent_arena);
ExprBatch* batch_obj = expr_batch_new(persistent_arena);
expr_batch_add_expression(batch_obj, "sin(x) + sqrt(y)");
expr_batch_add_variable(batch_obj, "x", 0.0);
expr_batch_add_variable(batch_obj, "y", 1.0);
expr_batch_evaluate(batch_obj, ctx2);
for (int i = 0; i < evals_per_batch; i++) {
expr_batch_set_variable(batch_obj, 0, (Real)i * 0.01);
expr_batch_evaluate(batch_obj, ctx2);
}
expr_batch_free(batch_obj);
double end_time = get_time_us();
memory_stats_t stats = get_memory_stats();
method2_total_time += (end_time - start_time);
method2_total_allocs += stats.total_allocs;
if (stats.peak_bytes > method2_peak_memory) {
method2_peak_memory = stats.peak_bytes;
}
disable_allocation_tracking();
}
printf("\n=== COMPARISON RESULTS ===\n");
printf("Method 1 (Complete Recreation):\n");
printf(" Total time: %.2f ms (%.2f ms/batch)\n",
method1_total_time / 1000.0, method1_total_time / (1000.0 * test_batches));
printf(" Total allocations: %zu (%.1f/batch)\n",
method1_total_allocs, (double)method1_total_allocs / test_batches);
printf(" Peak memory: %zu bytes (%.1f KB)\n",
method1_peak_memory, method1_peak_memory / 1024.0);
printf("\nMethod 2 (Arena Reset):\n");
printf(" Total time: %.2f ms (%.2f ms/batch)\n",
method2_total_time / 1000.0, method2_total_time / (1000.0 * test_batches));
printf(" Total allocations: %zu (%.1f/batch)\n",
method2_total_allocs, (double)method2_total_allocs / test_batches);
printf(" Peak memory: %zu bytes (%.1f KB)\n",
method2_peak_memory, method2_peak_memory / 1024.0);
double time_ratio = method1_total_time / method2_total_time;
double alloc_ratio = (double)method1_total_allocs / (method2_total_allocs > 0 ? method2_total_allocs : 1);
printf("\nMethod 2 is %.2fx faster than Method 1\n", time_ratio);
printf("Method 1 uses %.2fx more allocations than Method 2\n", alloc_ratio);
if (time_ratio > 1.1) {
printf("✓ Method 2 (Arena Reset) is significantly faster\n");
} else {
printf("~ Performance difference is minimal\n");
}
expr_arena_free(persistent_arena);
expr_context_free(ctx1);
expr_context_free(ctx2);
printf("\n");
}
int main() {
printf("\n==== Arena Integration Tests with Memory Tracking ====\n\n");
test_custom_allocator_integration();
test_arena_lifecycle();
test_batch_builder_with_arena();
test_arena_reset_reuse();
test_benchmark_expressions(); test_zero_allocations();
test_arena_size_estimation();
test_error_handling();
test_method1_complete_recreation();
test_method2_arena_reset();
test_memory_lifecycle_comparison();
printf("==== All Tests Passed! ====\n\n");
return 0;
}