CookMem 1.4.0
A C++ memory context / allocator
Loading...
Searching...
No Matches
CookMem

Build StatuscodecovApache License, Version 2.0

CookMem is a general-purpose memory context and allocator written in C++11. It can be cleaned up quickly by freeing segments rather than freeing allocations individually.

Unlike projects such as nedmalloc, which is intended to replace malloc / free in multithreaded applications, CookMem focuses on single-thread performance. It is intended for use within a thread in an MPP process, avoiding synchronization costs for most allocations and deallocations. There are other use cases as well.

CookMem separates the logic for obtaining large memory segments, allowing users to easily create a version of the memory context for their specific needs.

The algorithms and some code used here are based on dlmalloc. I took the time to understand dlmalloc and rewrite its logic in C++. Some key calculations remain the same as in dlmalloc because there is no need to reinvent the wheel.

Quick Start

CookMem is header-only. Add the include directory to your include path and include cookmem.h.

#include <cookmem.h>
#include <vector>
int main ()
{
// A memory context that obtains large memory segments from the system
// (mmap or VirtualAlloc) and caches released segments for reuse.
// Allocate and deallocate like malloc and free.
void* ptr = memCtx.allocate (100);
memCtx.deallocate (ptr);
// Memory with a larger alignment, for example for SIMD data.
void* simd = memCtx.allocateAligned (256, 64);
memCtx.deallocate (simd);
// Standard containers can allocate from the same context.
cookmem::Allocator<int> allocator (memCtx);
std::vector<int, cookmem::Allocator<int>> numbers (allocator);
numbers.push_back (42);
return 0;
} // Destroying memCtx releases all of its memory at once.
Definition allocator.h:39

See the tutorials for more.

Requirements

  • A C++11 compiler. CookMem is tested with GCC and Clang on Linux (x64, i386 and arm64), and with Visual Studio 2022 (MSVC and clang-cl) on Windows (x64 and Win32).
  • CMake 3.14 or later, to use the cookmem::cookmem target or to build the tests, examples and documentation. CMake is not needed to use the headers directly. See Installation and Build Instructions.

Performance

CookMem is faster than dlmalloc in most of the tests below: about 13 times faster for large blocks that come straight from the arena (perf_cookmem_10), about 5 times faster for interleaved realloc growth, where blocks grow in place (perf_cookmem_9), and about the same in the others. dlmalloc is faster for a sliding window of blocks of mixed sizes (perf_cookmem_11). Neither dlmalloc nor CookMem uses locks. CookMem was tested with both of its large chunk bins: the red-black tree (RB-tree), which is the default, and the AVL tree.

An interactive chart of these results is available with the documentation.

The tests were run on an AMD Ryzen 7 5800H with Ubuntu 24.04 (WSL2), built by GCC 13.3 with -O3. Times vary between runs by about 10%, so small differences are not significant. Each test was run 5 times. For each column, the best and the worst times were dropped and the remaining times were averaged. The numbers are in seconds.

Test What it covers dlmalloc CookMem (RB-tree) CookMem (AVL-tree)
perf_cookmem_1 Copy a 200-character string into a vector 1M times (small bins, plus large vector growth) 0.179852 0.147207 0.138198
perf_cookmem_2 Same as perf_cookmem_1, then clear and fill again (reuse of freed chunks) 0.328629 0.190954 0.177236
perf_cookmem_3 10k strings of growing length 1 to 10000 (mostly tree bins) 0.028434 0.026320 0.026967
perf_cookmem_4 Same as perf_cookmem_3, then clear and build again (reuse of freed tree chunks) 0.045815 0.032542 0.031780
perf_cookmem_5 Small same-size churn (std::list<int>, small bins) 0.108232 0.086955 0.095069
perf_cookmem_6 Mixed sizes (std::map<int, std::string>, values 0-999 chars, erase and re-insert) 0.162361 0.174398 0.159243
perf_cookmem_7 50k free chunks of the same 1KB size, then merging with free neighbors 0.058387 0.028435 0.030270
perf_cookmem_8 Fragmentation and best-fit search (sizes 256 B-8 KB, refill the holes) 0.105328 0.093511 0.097044
perf_cookmem_9 Interleaved realloc growth up to 32KB 0.503400 0.088520 0.092882
perf_cookmem_10 Large blocks (128KB-4MB) going straight to the arena 0.039517 0.003039 0.002812
perf_cookmem_11 Sliding window (FIFO) of 1024 live blocks, 16-2047 bytes 0.094661 0.117984 0.122740
perf_cookmem_12 10k vectors grown in interleaved order 0.028886 0.026630 0.028787

Use Cases

Avoiding Synchronization Cost

In multithreaded applications, malloc / free or new / delete must synchronize on each call. This synchronization cost can be significant. nedmalloc exists to reduce the synchronization costs of naive implementations and thereby increase performance.

For performance-critical applications, eliminating much of this synchronization cost can be even more beneficial. With CookMem, obtaining large segments using calls such as mmap may require synchronization, but allocations within those segments do not. See Thread-local and Scoped Context STL Allocators.

Avoiding Individual Frees

There are two sub-cases.

  1. Doing individual frees can reduce performance.
  2. A complex pointer graph containing trees or cycles can make it very difficult to free pointers in the correct order. Yet, the memory has to be released to avoid memory leaks.

Both cases can be addressed easily by using any of the MemContext classes. Destroying a memory context releases all of its memory at once. See Detailed Behavior for the details.

Restricting Memory Usage

For long-running programs that take user inputs, it is possible that even when all input values are validated, edge cases in calculations can still cause the program to accidentally request a very large amount of memory and crash. CookMem provides several ways to restrict memory usage. One can set a memory footprint limit or add code to the segment request logic to restrict large memory requests. See Restricting Memory Usage.

Diagnosing and Debugging

CookMem has built-in diagnostic tools that can help gather memory statistics and detect difficult-to-catch out-of-bounds writes without significantly slowing down the program. It also detects attempts to free an allocation more than once. CookMem is not a Valgrind replacement, but in many cases it can help catch difficult bugs. See Memory Statistics and Memory Corruption Detection.

Links