Mesh is a groundbreaking memory allocator that performs compaction without relocation for unmodified C/C++ applications. Published at PLDI 2019 by Bobby Powers, David Tench, Emery D. Berger, and Andrew McGregor from the University of Massachusetts Amherst, Mesh solves the long-standing problem of memory fragmentation in unmanaged languages.
Unlike garbage-collected languages where objects can be relocated and pointers updated, C/C++ applications expose raw memory addresses that can be manipulated arbitrarily (hidden in integers, stored to disk, used in pointer arithmetic). This makes traditional compaction impossible. Mesh's breakthrough is achieving compaction without changing object addresses through a technique called "meshing."
- Catastrophic fragmentation: Robson showed that all traditional allocators can suffer memory consumption up to O(log(max_size/min_size)) times the actual requirement
- Example: An application allocating 16-byte and 128KB objects could consume 13× more memory than needed
- Real-world impact:
- 99% of Chrome crashes on low-end Android devices are due to out-of-memory conditions
- Firefox underwent a 5-year effort to reduce memory footprint
- Redis implements custom "active defragmentation" to combat fragmentation
- Objects cannot be safely relocated because:
- Programs can stash addresses in integers
- Store flags in low bits of aligned addresses
- Perform arithmetic on addresses
- Store addresses to disk and reload them later
- There's no way to find and update all references
Meshing consolidates the contents of multiple partially-filled pages onto a single physical page while keeping multiple virtual pages pointing to it. This is the core innovation that enables compaction without relocation.
- Find meshable pages: Two pages are meshable if their allocated objects don't overlap at the same offsets
- Copy and remap: Copy contents from one page to another, then update virtual-to-physical mappings so both virtual pages point to the same physical page
- Release memory: Return the now-unused physical page to the OS
- Virtual addresses unchanged: Objects remain at the same virtual addresses
- Physical memory consolidated: Multiple sparse virtual pages share dense physical pages
- Transparent to applications: No code changes or recompilation required
To ensure pages are likely to be meshable, Mesh uses randomized allocation instead of traditional sequential allocation.
If objects are allocated sequentially (bump-pointer style), pages are likely to have objects at the same offsets, preventing meshing. In the worst case, every page could have exactly one object at the same offset, making meshing impossible.
- Random placement: Objects are allocated uniformly at random across available offsets in a span
- Mathematical guarantee: Probability that all objects occupy the same offset = (1/b)^(n-1) where b = objects per span, n = number of spans
- Example: With 64 spans of 16-byte objects in 4K pages (256 objects/page), the probability of no meshing = 10^-152
Finding meshable pages efficiently is critical for runtime performance.
SplitMesher(S, t):
1. Split span list S into two halves: Sl and Sr
2. For each span in Sl:
- Probe up to t spans from Sr for meshing opportunities
- If meshable pair found, mesh them and remove from lists
3. Parameter t controls time/quality tradeoff (default: 64)
- Probabilistic guarantees: Finds approximation within factor of 1/2 of optimal with high probability
- Efficient runtime: O(n/q) where n = number of spans, q = probability two spans mesh
- Practical effectiveness: t=64 balances runtime overhead and meshing quality
Mesh is implemented as a drop-in replacement for malloc/free, requiring no source code changes. It can be used via:
- Static linking: Compile with
-lmesh - Dynamic loading: Set
LD_PRELOAD=libmesh.so(Linux) orDYLD_INSERT_LIBRARIES(macOS)
- Purpose: Manage physical spans of memory with metadata
- Key features:
- Allocation bitmap tracking occupied/free slots
- Support for meshing multiple virtual spans to one physical span
- Atomic operations for thread-safe non-local frees
- Size classes from segregated-fit allocation
- States:
- Attached: Owned by a thread-local heap for allocation
- Detached: Owned by global heap, available for meshing
- Purpose: Enable fast randomized allocation with low overhead
- Design: Fixed-size array of available offsets in random order
- Operations:
- Allocation: Pop next offset from vector (O(1))
- Deallocation: Push freed offset and perform one Fisher-Yates shuffle iteration
- Space efficiency:
- Only 1 byte per offset (max 256 objects per span)
- One shuffle vector per attached MiniHeap per thread
- Total overhead: ~2.8KB per thread for 24 size classes
- Thread-local: No synchronization needed, unlike bitmaps
- Purpose: Fast, lock-free allocation for small objects
- Components:
- Shuffle vectors for each size class
- Reference to global heap for refills
- Thread-local PRNG for randomization
- Allocation fast path:
- Pop from shuffle vector if available
- Refill from attached MiniHeaps if vector empty
- Request new MiniHeap from global heap if needed
- Purpose: Coordinate meshing, manage MiniHeaps, handle large allocations
- Responsibilities:
- Allocate/deallocate MiniHeaps for thread-local heaps
- Perform meshing operations across all size classes
- Handle non-local frees from other threads
- Manage large (>16KB) allocations directly
- Meshing coordination:
- Rate-limited (default: max once per 100ms)
- Skipped if last mesh freed <1MB
- Concurrent with normal allocation
- Purpose: Manage virtual and physical memory for meshing
- Key innovation: Uses file-backed shared mappings instead of anonymous memory
- Created via
memfd_create()(memory-only file) - Allows multiple virtual addresses to map same physical offset
- Enables atomic page table updates via
mmap()
- Created via
- Page management:
- Tracks ownership (MiniHeapID) for each page
- Maintains bins of free/used pages by size
- Implements
scavenge()to return pages to OS
Meshing runs concurrently with application threads, maintaining two invariants:
- Read correctness: Objects being relocated are always readable
- Write safety: Objects are never written during relocation
- Page protection: Before copying, source pages marked read-only via
mprotect() - Trap handler: Segfault handler waits for meshing to complete
- Atomic remapping: After copy, source pages remapped read/write
- Zero stop-the-world: No global synchronization required
// Core meshing check: do bitmaps overlap?
bool bitmapsMeshable(bitmap1, bitmap2):
return (bitmap1 & bitmap2) == 0
// Main orchestration
meshAllSizeClassesLocked():
1. Scavenge freed pages
2. For each size class:
- Flush thread-local free memory
- Run shiftedSplitting algorithm
- Consolidate meshable pairs
3. Update statistics and page tables- Small objects:
- Same size classes as jemalloc for ≤1024 bytes
- Power-of-two classes for 1024-16384 bytes
- Reduces internal fragmentation from rounding
- Large objects (>16KB):
- Page-aligned, individually managed
- Not considered for meshing
- Directly freed to OS when deallocated
- Span: Contiguous run of pages containing same-sized objects
- Occupancy tracking: Bins organized by fullness (75-99%, 50-74%, etc.)
- Random selection: Global heap randomly selects from bins for reuse
- Meshing candidates: Only spans with occupancy below threshold (configurable)
Given n binary strings of length b (representing allocation bitmaps), find a meshing that releases the maximum number of strings. This reduces to:
- Graph representation: Nodes = strings, edges = meshable pairs
- Optimization goal: Minimum clique cover (partition into fewest cliques)
- Complexity: NP-hard in general, but polynomial for constant string length
- Traditional allocators: Worst-case fragmentation of O(log(max_size/min_size))
- Mesh: With high probability, avoids catastrophic fragmentation
- Key insight: Meshing can redistribute memory between size classes
Given:
- n spans to mesh
- q = global probability two spans mesh
- t = probe limit parameter (default: 64)
Results:
- Quality: Finds ≥n(1-e^(-2k))/4 meshes with high probability where k=t×q
- Runtime: O(n×k/q) probes in worst case
- Practical impact: For reasonable q values, finds near-optimal meshing quickly
Without randomization, regular allocation patterns can prevent meshing entirely. Experiments show:
- No randomization: Only 3% heap reduction, 4% overhead
- With randomization: 19% heap reduction, worth the 10.7% overhead
- Independence: Edges in meshing graph are not fully independent (3-wise dependence)
- Triangle rarity: P(3 strings all mesh) << P(pairwise meshing)^3
- Implication: Can focus on finding pairs (matching) vs. larger cliques
- 16% RSS reduction vs. bundled jemalloc
- 530MB with Mesh vs. 632MB with mozjemalloc
- <1% performance impact on benchmark score
- Consistent lower memory throughout execution
- 39% RSS reduction automatically
- Matches Redis's custom "active defragmentation" savings
- 5.5× faster than Redis's built-in defragmentation
- No application modifications required
- Modest 2.4% average reduction (not allocation-intensive)
- 15% reduction for perlbench (allocation-intensive)
- 0.7% geometric mean runtime overhead
- Frequency: Rate-limited to once per 100ms by default
- Duration: Average 0.2ms, max 7.5ms (Firefox)
- Concurrent execution: No stop-the-world pauses
- Adaptive: Skips meshing if ineffective (<1MB freed)
- Fast path: Thread-local, lock-free via shuffle vectors
- Random allocation: ~2-3 additional instructions vs. bump pointer
- Cache behavior: Comparable to modern allocators
- Scalability: Thread-local heaps minimize contention
- Shuffle vectors: ~2.8KB total (24 size classes × ~120 bytes)
- Thread-local metadata: <1KB
- MiniHeap metadata: 64 bytes per MiniHeap
- Bitmap overhead: 1 bit per object slot
- Page tables: Standard virtual memory overhead
- Mesh rate:
mesh.check_period(default: 100ms) - Effectiveness threshold: Minimum bytes to free (default: 1MB)
- Probe limit (t): Balances quality vs. runtime (default: 64)
- Max meshes per iteration: Prevents excessive meshing time
- Shuffle on allocation: Always enabled for randomization
- Shuffle on free: Optional additional randomization
- Occupancy cutoff: Max fullness for meshing candidates (default: configurable)
Full meshing with randomization enabled. Best for:
- Long-running applications
- Memory-constrained environments
- Applications with fragmentation issues
Randomized allocation only. Useful for:
- Debugging allocation patterns
- Performance comparison
- Applications with regular allocation patterns
- Transparent huge pages: Should be disabled (conflicts with 4KB page granularity)
- Direct huge page allocation: Still supported via mmap interfaces
- Security features: Compatible with ASLR, DEP, etc.
- High fragmentation: Many partially-filled pages
- Long-running applications: Time for fragmentation to develop
- Mixed allocation sizes: Benefits from cross-size-class redistribution
- Memory-constrained environments: Where savings matter most
- Sequential allocation patterns: Without enough entropy for meshing
- Very large allocations: >16KB objects not meshed
- Short-lived programs: Insufficient time for meshing benefits
- Full pages: Nothing to compact when pages are dense
- Virtual address space: 2× consumption in worst case (unmeshed pages)
- TLB pressure: Multiple virtual pages per physical page
- Page faults: Initial access to meshed pages
- Memory bandwidth: Copying during mesh operations
- Linux: Full support (primary platform)
- macOS: Full support
- 64-bit only: Current implementation requires 64-bit address space
- Drop-in replacement: No code changes required
- Standard API: Full malloc/free/realloc/memalign support
- Statistics: mallctl API for runtime introspection
- Open source: Apache 2.0 license
- Compaction without relocation: Previously thought impossible for C/C++
- Virtual memory as abstraction layer: Leverages MMU for pointer stability
- Randomization for meshability: Probabilistic approach to fragmentation
- Breaks Robson bounds: First allocator to provably avoid worst-case fragmentation
- Cross-size-class redistribution: Unique ability to move memory between classes
- Formal analysis: Rigorous probabilistic guarantees on effectiveness
- Automatic memory savings: No application changes required
- Compatible with existing code: True drop-in replacement
- Solves real problems: Demonstrated savings in Firefox and Redis
Mesh represents a breakthrough in memory management for unmanaged languages, achieving what was previously thought impossible: automatic compaction for C/C++ applications. Through the novel combination of meshing (virtual page remapping), randomized allocation, and efficient mesh search algorithms, Mesh provides:
- Significant memory savings: 16-39% reduction in real applications
- Theoretical guarantees: Breaks classical fragmentation bounds
- Practical deployment: Drop-in replacement requiring no code changes
- Low overhead: <1% performance impact in most cases
The key innovation—compaction without relocation—opens new possibilities for memory management in systems programming, potentially influencing future language runtimes and operating system designs.