当前位置: 首页 > news >正文

深入剖析 Chrome PartitionAlloc 内存池源码原理与性能调优实践

一、前言

在现代浏览器中,内存分配的性能和稳定性直接影响整体用户体验。Chromium/Chrome 作为世界上最复杂的 C++ 应用之一,为了高效、安全地管理内存,自研了一套高性能的内存池机制——PartitionAlloc

PartitionAlloc 在 Chrome 中几乎无处不在:DOM 节点的分配、V8 堆外内存、IPC 消息缓冲、UI 渲染对象,都可能通过它来完成。与传统 malloc / free 相比,它更强调:

  • 内存安全:结合 BackupRefPtr (BRP)、Pointer Scanning (PCScan) 等机制,降低 UAF 风险。

  • 性能优化:引入线程缓存(ThreadCache)、批量分配(FillBucket)、快速路径分配,减少锁争用和系统调用。

  • 碎片控制:通过 SlotSpan / Bucket / SuperPage 的层次化管理,降低内存浪费。

  • 跨平台一致性:统一抽象在 Windows、Linux、macOS 上的虚拟内存分配。

这篇文章将从源码视角深入解析 Chrome PartitionAlloc 内存池的机制,并结合实际工程调优经验,给出一份“从架构到实战”的完整指南。


二、PartitionAlloc 内存池架构解析

PartitionAlloc 的整体设计分为三层:

  1. SuperPage (2MB)

    • 内存分配的基本单位,每次至少申请 2MB。

    • SuperPage 的首尾部分保留作为保护页(guard page)。

    • 在中间区域,划分多个 SlotSpan

  2. SlotSpan (由多个系统页组成)

    • 每个 SlotSpan 管理一组等大小的对象(slot)。

    • SlotSpan 的元数据(metadata)记录 freelist、slot 数量等信息。

  3. Bucket (桶)

    • 每个 bucket 管理相同大小的 slot。

    • 典型分布:8B、16B、32B…到 256KB。

    • 大于 256KB 的分配采用 DirectMap,直接映射单独的大块内存。

关键常量定义

constexpr size_t PartitionPageSize = 16 * 1024; // 16KB constexpr size_t kSuperPageSize = 2 * 1024 * 1024; // 2MB constexpr size_t SystemPageSize = 4 * 1024; // 4KB 
  • PartitionPageSize (16KB):内部元数据和 SlotSpan 的最小粒度。

  • kSuperPageSize (2MB):每次分配的大块内存。

  • SystemPageSize (4KB):操作系统页面大小。


三、源码关键类说明

PartitionAlloc 的核心代码位于 base/allocator/partition_allocator/,几个重要类:

1. PartitionRoot

  • 整个内存池的入口类。

  • 负责初始化 bucket、管理 SuperPage。

  • 接口:

    • AllocFromBucket():分配对象。

    • Free():释放对象。

2. PartitionBucket

  • 管理一组固定大小的对象 slot。

  • 内部维护活跃 SlotSpan 链表:

    • active_slot_spans_head

    • empty_slot_spans_head

    • decommitted_slot_spans_head

  • 快速路径:直接从 freelist 取内存。

  • 慢速路径:触发新建 SlotSpan 或 SuperPage。

3. SlotSpanMetadata

  • 管理 SlotSpan 的元信息。

  • 字段:

    • freelist_head:指向空闲 slot。

    • num_allocated_slots:当前分配数量。

    • num_unprovisioned_slots:剩余可分配 slot 数。

4. ThreadCache

  • 线程本地缓存,减少锁争用。

  • 接口:

    • GetFromCache():快速分配。

    • MaybePutInCache():回收时加入缓存。

    • RunPeriodicPurge():周期清理(默认 2s)。

5. PartitionDirectMap

  • 处理大于 256KB 的分配,直接 mmap/VirtualAlloc

  • 分配粒度对齐到系统页。


四、SuperPage / SlotSpan / FreeList 图示

下面给出一个结构示意图,展示 SuperPage → SlotSpan → Slot → FreeList 的关系:

+-----------------------------------------------------------+ | SuperPage (2MB) | | | | [Guard Page 16KB] | | +-------------------+ | | | Metadata Area | <-- SlotSpanMetadata | | +-------------------+ | | | SlotSpan #1 | [slots of size 64B] | | | +---------+ | | | | | slot_1 | --> freelist_head → slot_3 → slot_7 ... | | | +---------+ | | | | slot_2 | | | | +---------+ | | +-------------------+ | | | SlotSpan #2 | [slots of size 128B] | | +-------------------+ | | | | [Guard Page 16KB] | +-----------------------------------------------------------+ 
  • freelist_head 存储在 SlotSpanMetadata 内部。

  • 每个空闲 slot 的起始字节存储下一个空闲 slot 的地址(单链表)。

  • 内存布局高度紧凑,减少碎片。


五、内存分配流程

Chrome 的分配流程可以分为三步:

  1. 调整大小

    • 向上对齐到对应 bucket 的 slot 大小。

    • 预留空间存放 cookie、guard byte。

  2. 分配

    • 线程缓存ThreadCache::GetFromCache()

    • 分配器快速路径PartitionRoot::AllocFromBucket(),直接 freelist 弹出。

    • 慢速路径PartitionBucket::SlowPathAlloc()

  3. 初始化

    • 如有需要,零初始化。

    • 调试模式下写入 cookie。

源码片段:

void* PartitionRoot::Alloc(size_t size) { PartitionBucket* bucket = SizeToBucket(size); SlotSpanMetadata* slot_span = bucket->active_slot_spans_head; if (slot_span && slot_span->freelist_head) { return slot_span->freelist_head->Pop(); } return SlowPathAlloc(bucket, size); } 

六、内存释放机制

释放同样分为 线程缓存 → 普通释放 → 慢速路径 → 系统回收

  1. 线程缓存

    • ThreadCache::MaybePutInCache()

    • 如果缓存已满,批量返还给 PartitionRoot。

  2. 普通释放

    • 插入 SlotSpan 的 freelist 头部。

    • 如果 Span 曾经标记为满,则重新挂回活跃列表。

  3. 慢速路径

    • 如果 Span 全部释放,迁入 empty_list。

    • 可能触发 Decommit,释放物理内存。

    • 对大块内存(DirectMap)直接 munmap / VirtualFree

  4. 定期释放

    • MemoryReclaimer::Reclaim(),默认 4s 周期运行。

    • 释放未使用的系统页,降低 RSS。


七、性能调优方向

在工程落地时,可以从以下几个方面优化:

  1. 线程缓存大小

    • 过大:浪费内存。

    • 过小:频繁 fallback,增加锁开销。

    • 调整策略:通过 ThreadCache::SetThreadCacheSizeLimits() 调优。

  2. 延迟回收策略

    • 默认 4s 的 MemoryReclaimer 可以缩短或延长。

    • 高频分配/释放场景适合更快周期。

  3. 大对象分配 DirectMap

    • 对于 >256KB 的 DirectMap,避免过度分配。

    • 可通过分配对齐策略减少内部碎片。

  4. BRP 与 PCScan 的选择

    • BRP (BackupRefPtr):运行时检测 UAF,增加内存开销。

    • PCScan (Pointer Scanning):周期扫描指针,适合大内存场景。

    • 可在 GN args 中按需启用:

      use_backup_ref_ptr = true enable_pointer_compression = true enable_pcscan = false 
  5. 统计与监控

    • 启用 tracing,观察 MemoryInfra.PartitionAlloc

    • 调用 allocator hooks,收集实时分配情况。


八、实战步骤

1. 本地构建带 BRP 的 Chromium

gn gen out/brp --args="is_debug=false use_backup_ref_ptr=true" ninja -C out/brp chrome 

2. 启用 PCScan

gn gen out/pcscan --args="enable_pcscan=true" 

3. 收集运行时统计

  • 方式 1:Tracing
    打开 about://tracing,勾选 MemoryInfraPartitionAlloc

  • 方式 2:Histogram
    在代码中添加:

    UMA_HISTOGRAM_COUNTS_1000("PartitionAlloc.AllocSize", size); 
  • 方式 3:Allocator hooks

    base::allocator::SetHooks(&my_hooks); 

九、工程落地建议

如果你希望将 PartitionAlloc 引入到非浏览器项目(如嵌入式/服务端组件):

  1. 构建系统

    • GN: 保留 partition_alloc/partition_alloc.gni

    • CMake: 需要手动导入 .cc/.h 文件,并定义 PA_BUILDFLAG(...)

  2. 启用/禁用建议

    • 禁用 allocator_shim:避免和系统 malloc 冲突。

    • 禁用 sanitizers:ASan 下 PartitionAlloc 与系统 malloc 共存会冲突。

    • PGO/LTO:需要在 GN args 中设置 use_partition_alloc=true

  3. 兼容性

    • Windows: 使用 VirtualAlloc

    • Linux: 使用 mmap + madvise

    • macOS: 使用 mmap + VM tag。


十、常见问题 FAQ

Q1: 为什么分配了 1KB 内存,实际 RSS 增加了 16KB?

  • 因为最小粒度是 PartitionPage (16KB),小分配被批量管理。

Q2: 为什么大于 256KB 的对象不走内存池?

  • 设计选择,避免巨型对象污染小对象池。

Q3: 如何判断某个对象是 DirectMap 分配?

  • 通过 PartitionAllocGetDirectMapMetadata(ptr)

Q4: ThreadCache 会导致内存泄漏吗?

  • 不会。ThreadCache 有周期清理机制,且在线程退出时自动回收。

Q5: 如何在生产环境观察内存池状态?

  • 开启 --enable-memory-infra,结合 Chrome 内置 DevTools → Performance → Memory。


十一、总结

通过 PartitionAlloc,Chromium 实现了一套跨平台、高性能、安全的内存池机制。

  • 架构层面:SuperPage → SlotSpan → Bucket → FreeList,层次分明,碎片可控。

  • 源码实现PartitionRoot 负责分配,ThreadCache 提升性能,MemoryReclaimer 负责回收。

  • 调优方向:可通过缓存大小、回收周期、DirectMap 策略优化性能。

  • 工程落地:不仅适合浏览器,也可以用于服务端、嵌入式系统,提供稳定高效的内存分配。

未来,随着 Chrome 在 隐私沙盒、性能优化、内存安全 方面的持续投入,PartitionAlloc 也会不断演进,例如更智能的垃圾回收、更轻量的指针保护。

对于开发者而言,理解 PartitionAlloc 的原理和源码实现,不仅能帮助我们优化浏览器,还能将其经验迁移到其他高性能项目中。


文章转载自:

http://VpdYKWkT.xjwtq.cn
http://5IZ3K1lg.xjwtq.cn
http://wZPcJZB8.xjwtq.cn
http://WWfHmmpd.xjwtq.cn
http://UzhD8I6g.xjwtq.cn
http://qDhliMiK.xjwtq.cn
http://rVr4YpxO.xjwtq.cn
http://IiDdIunI.xjwtq.cn
http://AIZ813hB.xjwtq.cn
http://l3EVO747.xjwtq.cn
http://WMKSILnO.xjwtq.cn
http://nHk0oQkC.xjwtq.cn
http://ygM9MbDa.xjwtq.cn
http://mtZO9nBG.xjwtq.cn
http://Wg9B17rM.xjwtq.cn
http://DlphGbqL.xjwtq.cn
http://100OFXwp.xjwtq.cn
http://z4mTqbX4.xjwtq.cn
http://oq6EiDEp.xjwtq.cn
http://5LOcvMOa.xjwtq.cn
http://YeLWRGow.xjwtq.cn
http://rQ0YXW4b.xjwtq.cn
http://4Ffxpu9z.xjwtq.cn
http://SYjjLqBB.xjwtq.cn
http://Hsvz8zU6.xjwtq.cn
http://AnTQOPlN.xjwtq.cn
http://Vjq9wpAd.xjwtq.cn
http://9Av1s0y2.xjwtq.cn
http://qXjLauHU.xjwtq.cn
http://Ov5zbdvf.xjwtq.cn
http://www.dtcms.com/a/377534.html

相关文章:

  • Shell 脚本编程:函数
  • C++ STL 容器的一个重要成员函数——`emplace_back`
  • vue3:触发自动el-input输入框焦点
  • python range函数练习题
  • Q2(门座式)起重机司机的理论知识考试考哪些内容?
  • 企业微信消息推送
  • 顺序表:数据结构中的基础线性存储结构
  • 什么是X11转发?
  • OpenCV计算机视觉实战(24)——目标追踪算法
  • 4.2 I2C通信协议
  • Spring Boot 读取 YAML 配置文件
  • 【系统分析师】第20章-关键技术:微服务系统分析与设计(核心总结)
  • SAP-MM:SAP MM模块精髓:仓储地点(Storage Location)完全指南图文详解
  • Shell脚本周考习题及答案
  • 广东省省考备考(第九十六天9.10)——言语(刷题巩固第二节课)
  • Pthread定时锁与读写锁详解
  • Go模块自动导入教学文档
  • 技术文章大纲:开学季干货——知识梳理与经验分享
  • TensorFlow平台介绍
  • Vue3 中实现按钮级权限控制的最佳实践:从指令到组件的完整方案
  • 生成模型与概率分布基础
  • Cookie之domain
  • JavaSSM框架-MyBatis 框架(五)
  • 中州养老:设备管理介绍
  • 【Day 51|52 】Linux-tomcat
  • MySQL - 如果没有事务还要锁吗?
  • “高德点评”上线,阿里再战本地生活
  • JUC的常见类、多线程环境使用集合类
  • 《C++ 108好库》之1 chrono时间库和ctime库
  • C++篇(7)string类的模拟实现