LevelDB 源码|Arena内存池的设计

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) {
// 禁止0字节分配
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) {
// 如果需要的内存大于默认一个Block大小的1/4,
// 则单独分配一个刚好 bytes 大小的内存。
if (bytes > kBlockSize / 4) {
// Object is more than a quarter of our block size. Allocate it separately
// to avoid wasting too much space in leftover bytes.
char* result = AllocateNewBlock(bytes);
return result;
}

// We waste the remaining space in the current block.
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
8     = 1000
8 - 1 = 0111

因此可以通过:

1
address & (align - 1)

快速得到:

1
address % align

从而计算当前地址距离下一个对齐地址还差多少字节。

例如:

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
// 分配 bytes 内存,并保证按 align 对齐。
char* Arena::AllocateAligned(size_t bytes) {
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
// 保证 align 是2的幂。
static_assert((align & (align - 1)) == 0,
"Pointer size should be a power of 2");
// 相当于alloc_ptr_ % align;找当前地址相对上一个 align 对齐地址的偏移
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
// slop -> padding。即到下一个对齐地址的padding。
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 {
// AllocateFallback always returned aligned memory
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 的幂进行位运算优化


LevelDB 源码|Arena内存池的设计
http://example.com/2026/09/09/LevelDB-源码|Arena内存池的设计/
作者
lorixyu
发布于
2026年9月9日
许可协议