LevelDB 中有一个小的组件:Arena。
它的作用很简单:批量管理内存,避免频繁调用 new / malloc,同时尽量降低内存碎片和分配开销。
1. 基本思路 Arena 会一次申请一块默认 Block = 4096 字节的内存,然后通过移动指针的方式不断切分:
每次申请内存时,只需要:
1 2 result = alloc_ptr_; alloc_ptr_ += bytes;
相比频繁调用系统分配器,这种方式非常轻量。
2. 为什么区分大对象和小对象 当当前 Block 剩余空间不够时,LevelDB 会进入 AllocateFallback()。
1 2 3 4 5 6 7 8 9 10 11 inline char * Arena::Allocate (size_t bytes) { assert (bytes > 0 ); if (bytes <= alloc_bytes_remaining_) { char * result = alloc_ptr_; alloc_ptr_ += bytes; alloc_bytes_remaining_ -= bytes; return result; } return AllocateFallback (bytes); }
进入AllocateFallback()后 如果申请的是小对象,就重新申请一个固定大小的 Block,并继续从中分配。
如果对象大小超过:kBlockSize / 4 则单独为它申请一块内存。
原因是如果一个大对象直接占据普通 Block,就会容易留下大量无法有效利用的剩余空间。
因此 LevelDB 选择让大对象独立分配,而把普通 Block 主要留给大量小对象使用。
这是一种典型的工程权衡:
小对象批量分配,大对象单独处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 char * Arena::AllocateFallback (size_t bytes) { if (bytes > kBlockSize / 4 ) { char * result = AllocateNewBlock (bytes); return result; } alloc_ptr_ = AllocateNewBlock (kBlockSize); alloc_bytes_remaining_ = kBlockSize; char * result = alloc_ptr_; alloc_ptr_ += bytes; alloc_bytes_remaining_ -= bytes; return result; }
3. 内存对齐 Arena 还提供了: AllocateAligned()
用于返回满足对齐要求的地址。
LevelDB 保证对齐粒度至少为 8 字节:
1 const int align = (sizeof (void *) > 8 ) ? sizeof (void *) : 8 ;
以满足指针、uint64_t、double 等常见类型的对齐需求。
为了快速计算对齐位置,LevelDB 要求 align 是 2 的幂:
1 static_assert ((align & (align - 1 )) == 0 );
如果 align = 8:
因此可以通过:
快速得到:
从而计算当前地址距离下一个对齐地址还差多少字节。
例如:
1 2 3 4 5 6 7 8 9 10 当前地址:1003 align:8 1003 % 8 = 3 需要跳过: 8 - 3 = 5 bytes 最终地址: 1008
这部分被跳过的空间就是 padding。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 char * Arena::AllocateAligned (size_t bytes) { const int align = (sizeof (void *) > 8 ) ? sizeof (void *) : 8 ; static_assert ((align & (align - 1 )) == 0 , "Pointer size should be a power of 2" ); size_t current_mod = reinterpret_cast <uintptr_t >(alloc_ptr_) & (align - 1 ); size_t slop = (current_mod == 0 ? 0 : align - current_mod); size_t needed = bytes + slop; char * result; if (needed <= alloc_bytes_remaining_) { result = alloc_ptr_ + slop; alloc_ptr_ += needed; alloc_bytes_remaining_ -= needed; } else { result = AllocateFallback (bytes); } assert ((reinterpret_cast <uintptr_t >(result) & (align - 1 )) == 0 ); return result; }
4. Arena 的特点 LevelDB 的 Arena 是一个简单的内存分配器。
它不会维护空闲链表,也不会尝试回收单个对象,而是采用非常简单的策略:
1 2 3 4 5 申请 Block ↓ 不断移动 alloc_ptr_ ↓ Arena 析构时统一释放
这种设计牺牲了一部分内存利用率,换来了:
极低的分配开销
简单的实现
较好的缓存局部性
避免频繁 malloc/free
适合 LevelDB 中生命周期接近、数量较多的小对象。
总结
bump pointer 分配
大小对象分流
用空间换时间
内存对齐
利用 2 的幂进行位运算优化