fangpsh's blog

禁用透明大页

tl;dr(长话短说)

“透明大页(Transparent Hugepages)”是一个Linux 内核特性,它通过提高处理器的内存映射硬件的使用效率(译注:降低TLB Miss 和page fault,提高TLB 的命中率,这部分基础知识可以翻下操作系统书)来获取更好的性能。在绝大多数Linux 发行版中是默认启用的(“enabled=always”)。

透明大页能让一些应用程序的性能提高一点点(最好的情况大约是10%,一般在0~3%),但是会造成很明显的性能问题(参考:1.mongodb,2.oracle, 3.splunk), 甚至会造成严重的内存泄漏(参考:1.digitalocean,2.golang/go).

为了避免这些问题,你应该将运行的服务器设置成enabled=madvise

echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled

并在服务器的内核命令行上设置transparent_hugepage=madvise(例如在/etc/default/grub 中设置)。

完成这些修改之后,应用程序能通过透明大页提升性能,也能避免上述问题(译注:设置成madvise,应用程序通过设置MADV_HUGEPAGE标志就能分配到大页,不需要的程序则不受影响)。

继续阅读了解更多细节。

什么是透明大页?

什么是大页?

数十年来,处理器和操作系统之间通过使用虚拟内存,在应用程序可见的内存空间(“虚拟内存空间(virtual address space)”)和底层的物理内存之间设置了间接层。间接层不仅保护了应用程序互相不受影响,还有许多强大的特性。

非常多的 x86 处理器都是通过一个叫页表(page table)的方案实现虚拟内存,这个方案会在内存中存一个非常大的映射表(实际上一个深度不同的树结构,不过也可以看作是一个稀疏表)。传统上,x86 处理器中一份页表条目对应一份4KB 的内存“页”。

虽然页表都是存在内存里,但是处理器会缓存一部分页表条目到处理器的寄存器上,它被称为TLB 。查看我笔记本上的cpuid(1)(译注:Linux tool to dump x86 CPUID information about the CPU(s))发现最低等级的TLB 只包含64个条目,每个条目对应一份4KB 的数据页。现在是2017年,64*4KB 只有四分之一兆字节,远小于目前使用的大多数应用程序的工作内存。这种大小不匹配的情况意味着占用大量内存的应用程序会周期性的遇到TLB 不命中的情况,从而需要花费很高的代价访问主内存,只为得到具体的内存地址(译注:TLB Miss 之后需要访问内存中的页表,从而得到具体内存地址)。

为了改进TLB 的效率,x86 及其他处理器长期以来都支持创建“大页(huge pages)”,大页的页表条目能映射一大段的物理内存地址。根据操作系统的配置不同,大多数最近的芯片能够映射2MB,4MB,甚至1GB 的内存页。使用大页意味着TLB 存着更多的数据,对某些特定的任务来说效率更高。

什么是透明大页?

存在各种页表管理方式,这意味着操作系统需要决定如何映射地址空间和物理内存。由于应用程序的内存管理接口(例如mmap(2))一直都是基于最小的 4KB 页,所以内核映射数据必须以4KB 为单位。最简单和最灵活的(就已支持的内存布局而言)方案是只采用4KB 的页,应用程序映射内存无法使用大页。长期以来,这是内核最通用的内存管理策略。

对于需要大量内存并对性能敏感的应用程序(例如某些特定数据库或者科学计算程序),内核引入hugetlbfs 特性,该特性允许系统管理员通过配置让特定的应用程序使用大页。

透明大页(简称“THP”),正如其名,旨在自动为应用程序提供大页支持,不需要特殊配置(译注:透明大页的透明,类似透明代理的透明)。透明大页通过在后台扫描(使用khugepaged 内核线程)内存映射,尝试找到或者创建(通过移动相邻的内存)总共2MB 的连续4KB 映射,用一个大页来替换这一段内存映射。

有什么问题?

透明大页运行良好时,特定的测试场景下,可以带来大约10% 的性能提升。然而,它也会造成至少两种非常严重的故障:

内存泄漏

THP 倾向于创建2MB 的内存映射。然而,这样做太贪心,即使必要的情况下,也不愿意把它们拆分回去。如果一个应用程序映射了一大段内存但是只访问前面几个字节,传统上只会消耗一个4KB 的物理内存页。THP 开启的情况下,khugepaged会将4KB 页扩张到2MB,内存占用量增大512倍(这份Bug 报告 中的例子更糟糕,甚至超过512 倍!)。

这种情况不是假设;Go 语言的GC 就有一个明确的解决方法,Digital Ocean 也记录了它们是如何处理Redis,THP 和jemalloc遇到的问题。

(译注:据说3.10 内核透明零页有泄漏问题,内核只释放2MB 中第一个4KB 的页面,剩余的页面泄漏)

卡顿和高CPU 使用率

应用程序都是分配相对静态的内存,稳定的状态下,khugepaged 的工作量是最小的。但是如果存在频繁映射内存的情况,或者存在生命周期很短的进程,khugepaged 会进行大量的拆分/合并内存区域的工作,毫无意义,存活时间很短。这会引起很高的CPU 使用率,以及较长的卡顿,因为内核被迫得先把2MB 的页拆分成4KB 的页,才能执行原本在单页上效率很高的操作。

因为这些原因,启用了THP 之后,好几个应用程序都观察到30% 的性能下降,甚至更糟。

现在怎么办?

THP 作者们事先意识到了透明大页可能有潜在的问题(尽管如今看来,他们低估了问题的严重性),所以他们选择通过 /sys/kernel/mm/transparent_hugepage/enabled 系统文件配置透明大页。

更重要的是,他们为透明大页实现了一种可选择的模式。将/sys/kernel/mm/transparent_hugepage/enabled 设置为madvisekhugepaged 默认情况下不会处理内存,除非应用程序使用madvise 系统调用,给特定范围的内存进行THP 处理。

由于在大多数情况下,只有少数特定的应用程序能通过透明大页显著提升性能,所以这是一个两全其美的选项。这些少数的应用程序可以选择使用madvise,其余的应用程序不受影响。

所以,我建议每个用户都把透明大页配置成madvise,如文章开头tl;dr所说的。同时我也希望说服主流的发行版默认禁用透明大页,让更多的系统管理员和开发者避免踩这些坑。