Libpas¶
Libpas 是一个可配置的内存分配器工具包,旨在支持 isoheaps 的采用。目前,Libpas 的主要客户端是 WebKit 的 bmalloc 项目,它被用作 bmalloc 所有功能(bmalloc::api、IsoHeap<> 和 Gigacage)的替代品。WebKit 的 ExecutableAllocator 也使用了 Libpas 的 jit_heap API。
概述¶
Libpas 是一个用于创建内存分配器的工具包。当作为动态库(dylib)构建时,它会暴露许多不同的内存分配器,具有从合理到不合理的各种配置,而不会覆盖 malloc 及其相关函数。对于生产用途,Libpas 旨在作为某个更大的 malloc 项目的一部分进行构建;例如,当 Libpas 看到 PAS_BMALLOC 宏时,它将提供 WebKit 的 bmalloc 库创建分配器所需的一切。
Libpas 的分配器工具包有三个构建块
-
隔离堆(segregated heap)。这实现了类似简单隔离存储的功能。粗略地说,大小类保存页面集合,每个页面只包含该大小的对象。隔离堆还支持页面拥有多个大小类,但这被用作“冷启动”模式以减少内部碎片。由于 Libpas 可以轻松高效地创建大量堆,并且堆针对只有一个大小类进行了特殊优化,因此隔离堆非常适合作为 isoheap 的实现。隔离堆的速度也超快。它们在对象流失性能方面优于原始 bmalloc。典型的隔离分配操作不需要使用任何原子操作或内存屏障,只需少量加载、一些加法(可能还有位运算)和几次存储。典型的隔离释放操作也是如此。
-
位拟合堆(bitfit heap)。这实现了使用每最小对齐粒度位的第一适配分配。位拟合堆空间效率极高,但速度远不如隔离堆。位拟合堆对它们可以处理的大小有上限,但可以配置为分配数百 KB 的对象。位拟合堆不适用于 isoheap。位拟合主要用于“边缘”分配(比隔离堆的中间大小大但比大型小)和 jit_heap。
-
大型堆(large heap)。这实现了基于笛卡尔树的第一适配分配器,用于任意大小的对象。大型堆适用于 isoheap;例如,它们会记住内存中每个空闲对象的类型,并记住原始对象边界的位置。
每个构建块都可以通过多种方式进行配置。Libpas 使用 C 模板编程风格,因此隔离堆和位拟合堆可以针对不同的页面大小、最小对齐、安全-性能权衡等进行配置。
所有堆都能够参与物理页共享。这意味着任何时候,只要堆管理的任何系统内存页完全空闲,它就符合通过解提交(decommit)返回给操作系统。Libpas 的解提交策略经过特别优化,以弥补 isoheaping 固有的内存开销。Libpas 实现了比 bmalloc 更好的内存使用率,因为它比 bmalloc 更早地返回页面,而且性能开销可忽略不计。例如,Libpas 可以配置为在页面空闲 300 毫秒后随时将其返回给操作系统。
Libpas 大量使用细粒度锁和复杂的锁调度。一些数据结构将由一组不同的锁中的任何一个进行保护,锁的获取涉及获取锁、检查是否获得了正确的锁,并可能重新循环。Libpas 的算法围绕以下原则设计:
-
降低任何长时间运行的操作需要持有任何频繁运行的操作可能需要的锁的可能性。例如,解提交一个页面需要持有锁并进行系统调用,但所持有的锁在任何其他操作期间被需要的可能性很低。这种“低可能性”虽然不保证,但通过许多不同的技巧得以实现。
-
降低连续运行的两个操作(都获取锁)需要获取不同锁的可能性。Libpas 有很多技巧,使得动态访问的数据结构能够适应使用相同的锁,从而减少锁获取的总次数。
最后,Libpas 旨在拥有大量测试,包括单元测试和模拟测试。虽然 Libpas 本身是用 C 语言编写的(以减少在 C 或 C++ 代码库中采用它的摩擦),但测试套件是用 C++ 编写的。理想情况下,Libpas 所做的每一件事都有测试。