按请求生命周期批量管理小对象内存
nginx 将小额分配变成池内游标推进,并以池的生命周期统一释放;较大的分配另建记录管理。
工程问题#
解析和处理 HTTP 请求会产生许多生命周期相近的小对象;逐个分配、追踪和释放会增加管理工作。
实际实现#
ngx_palloc 对不超过 pool->max 的分配调用 ngx_palloc_small:从 current 块开始,对齐 d.last 并检查空间,成功便推进游标。块不够时遍历后续块或分配新块;大对象由 ngx_palloc_large 单独分配并登记。
处理流程#
- 按大小与调试配置选择小对象池路径或大对象路径
- 对齐当前块游标,空间足够则返回旧游标并推进 d.last
- 空间不足时找后续池块或扩展;池销毁时运行清理回调并释放池块与大对象
源码片段#
固定版本 · 原文摘录154 p = pool->current;155156 do {157 m = p->d.last;158159 if (align) {160 m = ngx_align_ptr(m, NGX_ALIGNMENT);161 }162163 if ((size_t) (p->d.end - m) >= size) {164 p->d.last = m + size;165166 return m;167 }168169 p = p->d.next;170171 } while (p);172173 return ngx_palloc_block(pool, size);174}从 current 块取 d.last,按需对齐后检查剩余空间;命中便推进游标。否则沿块链表继续找,最终进入扩展池块的慢路径。
展示小对象分配的主体;大小分流、大对象登记和销毁逻辑在同一原始文件中。
收益与取舍#
工程分析带来的收益
- 小对象命中现有块时只需对齐、边界检查和游标更新
- 把大量对象的回收绑定到同一所有者,减少逐对象释放步骤
付出的代价
- 小对象通常不能用 ngx_pfree 单独归还,内存会保留到池重置或销毁
- 当前块空间不足时仍会遍历或分配,不能把所有分配都视为严格 O(1)
- 对象引用不得超出池的生命周期;对齐与未使用尾部也会消耗空间
可比较的选择
逐对象 malloc / free允许独立释放不同生命周期对象,但需要更细粒度的所有权与释放管理。
Slab / 对象缓存适合反复复用固定规格对象;与按生命周期整体释放的内存池目标不同。
版本与适用范围#
nginx release-1.26.3 的 ngx_pool_t;请求可持有自己的 pool,其他配置与连接对象也会使用池。大对象单独分配;NGX_DEBUG_PALLOC 可绕开常规小对象池路径。
参考来源#
源码、官方文档与原始论文src/core/ngx_palloc.c · ngx_palloc / ngx_palloc_small
src/core/ngx_palloc.c · ngx_destroy_pool / ngx_pfree
src/http/ngx_http_upstream_round_robin.c · rrp = ngx_palloc(r->pool, …)
相关实现与比较#
4 个相关案例伙伴分配器把空闲物理页组织为不同 order 的连续块,并用空闲链表管理同阶块。
limit_req 把漏桶计算、红黑树查状态与队列回收组合起来,按配置的 key 管理请求速率。
QEMU 的 QEMUTimerList 用有序单链表维护定时器,链表头就是下一到期项。
缓存块命中后可能需要从队列中间移除,双向链表让这一步保持常数时间。