From 06450003a5dc919fb1b0857a4418b982ab2ee35d Mon Sep 17 00:00:00 2001 From: dance858 Date: Fri, 4 Sep 2026 20:17:25 -0700 Subject: [PATCH 1/2] Make peak-memory tracking a compile-time option (SP_TRACK_MEMORY) The sp_malloc/sp_free counters g_allocated_bytes and g_peak_bytes were the only mutable global state in the library, and every allocation updated them without synchronization. They feed nothing but the "Peak memory" line in the verbose problem summary. Gate them behind a CMake option, SP_TRACK_MEMORY, default OFF. In the default build the sp_* wrappers are plain malloc/calloc/realloc/free and the globals do not exist, so the library carries no shared state and is safe to use from multiple threads as long as each expression tree is touched by one thread. Verbose output prints "n/a" for peak memory unless built with -DSP_TRACK_MEMORY=ON. Stays on C99. Alternative to #100, which used _Thread_local and required C11. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01FbYHhSoJ1BpEohQrjhdrG7 --- CMakeLists.txt | 7 +++++++ include/utils/tracked_alloc.h | 30 ++++++++++++++++++++++++++++++ src/problem.c | 11 +++++++++++ src/utils/tracked_alloc.c | 2 ++ 4 files changed, 50 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52c5f4c7..7f4f9615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,13 @@ file(GLOB_RECURSE SOURCES "src/*.c") # Create core library add_library(dnlp_diff ${SOURCES}) +# Optional peak-memory tracking via the sp_malloc/sp_free counters. OFF by default +# so the library carries no mutable global state. +option(SP_TRACK_MEMORY "Track peak memory usage via sp_malloc/sp_free counters" OFF) +if(SP_TRACK_MEMORY) + target_compile_definitions(dnlp_diff PUBLIC SP_TRACK_MEMORY) +endif() + # Link math library (Unix/Linux only) if(NOT MSVC) target_link_libraries(dnlp_diff m) diff --git a/include/utils/tracked_alloc.h b/include/utils/tracked_alloc.h index 50c3351d..c5f7b389 100644 --- a/include/utils/tracked_alloc.h +++ b/include/utils/tracked_alloc.h @@ -21,6 +21,10 @@ #include #include +/* Peak-memory tracking is only compiled in with -DSP_TRACK_MEMORY=ON (CMake + option). Otherwise sp_* are plain malloc/calloc/realloc/free. */ +#ifdef SP_TRACK_MEMORY + /* Platform shim for "how many usable bytes are at this malloc'd pointer". Used to track total live bytes */ #if defined(__APPLE__) @@ -95,4 +99,30 @@ static inline void *sp_realloc(void *ptr, size_t size) return new_ptr; } +#else /* !SP_TRACK_MEMORY */ + +/* Tracking disabled (default): the wrappers forward straight to libc and the + library has no mutable global state. */ +static inline void *sp_malloc(size_t size) +{ + return malloc(size); +} + +static inline void *sp_calloc(size_t count, size_t size) +{ + return calloc(count, size); +} + +static inline void sp_free(void *ptr) +{ + free(ptr); +} + +static inline void *sp_realloc(void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +#endif /* SP_TRACK_MEMORY */ + #endif /* TRACKED_ALLOC_H */ diff --git a/src/problem.c b/src/problem.c index 11b49f00..3362aa87 100644 --- a/src/problem.c +++ b/src/problem.c @@ -32,10 +32,12 @@ static void problem_lagrange_hess_fill_sparsity(problem *prob, int *iwork); problem *new_problem(expr *objective, expr **constraints, int n_constraints, bool verbose) { +#ifdef SP_TRACK_MEMORY /* we don't reset g_peak_bytes or g_allocated_bytes since allocations using sp_malloc/sp_calloc might have happened before new_problem in eg., left_matmul, and their frees will subtract from this counter. */ g_peak_bytes = g_allocated_bytes; +#endif problem *prob = (problem *) sp_calloc(1, sizeof(problem)); if (!prob) return NULL; @@ -305,6 +307,7 @@ void problem_init_derivatives(problem *prob) problem_init_hessian(prob); } +#ifdef SP_TRACK_MEMORY static inline void format_memory(size_t bytes, char *buf, size_t buf_size) { if (bytes < 1024) @@ -320,6 +323,7 @@ static inline void format_memory(size_t bytes, char *buf, size_t buf_size) snprintf(buf, buf_size, "%.2f MB", (double) bytes / (1024.0 * 1024.0)); } } +#endif static inline void print_end_message(const Diff_engine_stats *stats) { @@ -337,9 +341,14 @@ static inline void print_end_message(const Diff_engine_stats *stats) printf(" Affine constraints (nnz): %d\n", stats->nnz_affine); printf(" Jacobian nonlinear constraints (nnz): %d\n", stats->nnz_nonlinear); printf(" Lagrange Hessian (nnz): %d\n", stats->nnz_hessian); +#ifdef SP_TRACK_MEMORY char mem_buf[64]; format_memory(stats->memory_bytes, mem_buf, sizeof(mem_buf)); printf(" Peak memory: %s\n", mem_buf); +#else + printf(" Peak memory: n/a " + "(build with -DSP_TRACK_MEMORY=ON)\n"); +#endif printf("\nTiming (seconds):\n"); printf(" Derivative structure (sparsity): %8.3f\n", @@ -367,7 +376,9 @@ void free_problem(problem *prob) { if (prob == NULL) return; +#ifdef SP_TRACK_MEMORY prob->stats.memory_bytes = g_peak_bytes; +#endif if (prob->verbose) { print_end_message(&prob->stats); diff --git a/src/utils/tracked_alloc.c b/src/utils/tracked_alloc.c index 9b0f4d81..8caf72d9 100644 --- a/src/utils/tracked_alloc.c +++ b/src/utils/tracked_alloc.c @@ -17,5 +17,7 @@ */ #include "utils/tracked_alloc.h" +#ifdef SP_TRACK_MEMORY size_t g_allocated_bytes = 0; size_t g_peak_bytes = 0; +#endif From 4c7770e3aa943dc22b109a89488e724498e5fc49 Mon Sep 17 00:00:00 2001 From: dance858 Date: Fri, 4 Sep 2026 20:21:35 -0700 Subject: [PATCH 2/2] Drop the "Peak memory: n/a" line and fix clang-format violations With SP_TRACK_MEMORY off the verbose summary now simply omits the peak memory line. Also reformat permuted_dense_linalg.c, which was failing the clang-format CI check on main. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01FbYHhSoJ1BpEohQrjhdrG7 --- src/problem.c | 3 --- src/utils/permuted_dense_linalg.c | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/problem.c b/src/problem.c index 3362aa87..4b753b52 100644 --- a/src/problem.c +++ b/src/problem.c @@ -345,9 +345,6 @@ static inline void print_end_message(const Diff_engine_stats *stats) char mem_buf[64]; format_memory(stats->memory_bytes, mem_buf, sizeof(mem_buf)); printf(" Peak memory: %s\n", mem_buf); -#else - printf(" Peak memory: n/a " - "(build with -DSP_TRACK_MEMORY=ON)\n"); #endif printf("\nTiming (seconds):\n"); diff --git a/src/utils/permuted_dense_linalg.c b/src/utils/permuted_dense_linalg.c index 4fdf8067..e1703cd1 100644 --- a/src/utils/permuted_dense_linalg.c +++ b/src/utils/permuted_dense_linalg.c @@ -561,10 +561,10 @@ void BTDA_csc_pd_fill_values(const CSC_matrix *B, const double *d, #if defined(__GNUC__) || defined(__clang__) __attribute__((unused)) #endif -static void -BTDA_csc_pd_fill_values_via_transpose_dead(const CSC_matrix *B, const double *d, - const permuted_dense *A, - permuted_dense *C) +static void BTDA_csc_pd_fill_values_via_transpose_dead(const CSC_matrix *B, + const double *d, + const permuted_dense *A, + permuted_dense *C) { if (C->base.nnz == 0) {