0. 引言
继续来补技术债了,在大数据平台的系统,甚至一些数据库的系统配置要求中,往往会要求把透明大页内存关闭,关闭的方式如下: 命令行中执行,可以在当前环境生效:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
除此之外,还要在系统加载时生效,需要在/etc/rc.local加入以下内容
if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
echo never > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi
那么,在这些大数据平台和数据库中,为什么要把大页内存关闭呢,我们先从大页内存的原理谈起。
1. 什么是大页内存
大页内存是一种可以提高计算机性能的技术。在一般的计算机系统中,内存通常被划分成大小相等的小块,称为页。当计算机执行程序时,它会将程序需要的数据和指令分配到这些页中,然后再进行访问和处理。传统的页面大小通常是4KB,但是大页内存就是使用更大的页面大小。大页内存的最常见大小是2MB或4MB,它们对于特定的计算任务来说非常有用。大页内存在处理大型数据集或高性能计算应用中具有明显的优势。
2. 实现原理
大页内存利用了虚拟内存系统中的页表机制。通常情况下,每个进程都有自己的页表,用于将虚拟地址转换为物理地址。而大页内存通过将相邻的小页面组合成大页面,减少了页表的数量,从而减少了内存开销和TLB(Translation Lookaside Buffer)的查询次数,提高了内存访问的效率。
页面转换
页面转换流程是这样的:当我们的电脑运行程序时,操作系统会将内存划分成许多同样大小的页面(通常是4KB)。每个程序都会被分配到一些页面来存放代码和数据。当程序需要访问某个页面时,如果该页面不在内存中,操作系统就会执行页面转换。具体流程如下:
- 缺页中断: 当程序要访问的页面不在内存中时,CPU会触发一个缺页中断,告诉操作系统页面不在内存。
- 页面调度: 操作系统会从磁盘上选择一个空闲的页面帧或者一个不再使用的页面帧,将其加载到内存中,用来存放程序需要的页面。
- 更新页表: 当页面成功加载到内存中后,操作系统会更新进程的页表,将正确的物理页面映射到逻辑页面上。
- 恢复执行: 一切就绪后,程序会接着从中断中断的地方继续执行。
为什么大页内存能提高效率
页面转换会带来很大的开销,主要表现为以下两个方面:
- 时间开销:由于需要进行页表映射,从物理内存中寻找数据,而且还需要不断地更新页表,这些都需要额外耗费时间。
- 空间开销:由于每个进程都需要使用自己的页表,所以每个进程都需要维护一个独立的页表,这样就会浪费很多空间。
而至于为什么使用大页内存可以提高性能呢?这是因为大页内存可以减少内存管理的开销。相比于小页面,大页面意味着更少的页表项和更少的页面转换,这样就能减少CPU访问页表的时间以及缺页中断的次数,提高系统的整体性能。
使用大页内存的代价
大页内存是指将一页的大小从4KB增至2MB或更大,为了利用大页内存,每一个页表项就映射了更多的物理内存空间。使用了大页内存,也会带来一些代价:
- 内存碎片:由于大页内存占用的空间更多,因此页表所使用的内存空间也会变得更大,进而导致内存碎片的增加。
- 缺页处理开销:由于大页内存所占用的物理内存空间更大,如果该页内存中只有部分数据被使用,那么在发生页面错误时,操作系统需要将整个大页内存加载进入物理内存,这会增加缺页处理的时间开销。
举个例子,如果一个进程需要保留4KB内存的数据,而页大小是4KB,那么系统将为该进程分配一个单独的一页。如果页大小是2MB,那么该进程就会使用2MB的内存,而剩余的1.996MB就会被浪费掉,从而浪费了部分物理内存。
因此,虽然大页内存可以减少页表所占用的空间和页表更新开销,但会增加内存碎片和缺页处理开销,因此在使用大页内存时需要根据具体的应用场景进行慎重考虑。
3. 大页内存的分类
在Linux系统中,大页内存主要分为两类:静态大页内存和动态大页内存。
静态大页内存
静态大页内存是在系统启动时预留一块连续的物理内存区域,用于存储大页内存。操作系统会在需要的时候将这些大页分配给进程使用。这种方式适用于需要大块连续内存的应用,如数据库。 静态大页内存提高内存访问的效率,减少页表的管理开销,有助于提高应用程序的性能。但由于内存是固定的,因此在系统启动时要预留一定的内存空间,可能会导致内存的浪费。
动态大页内存
动态大页内存是在运行时自动分配和释放的,根据系统的负载、内存需求等动态调整大页的使用情况。这种方式适用于多样化的应用场景,能够在不同负载下灵活分配内存。 动态大页内存可以根据实际需求动态分配内存,可以更好地利用系统资源,减少内存的浪费。
4. 回到问题本身
通过以上的介绍,明白了我们禁用的实际上是透明大页内存。
关于透明大页
透明大页(transparent hugepage)是操作系统中常用的动态大页内存实现方式,几乎所有应用程序和操作系统都在虚拟内存中运行。虚拟内存映射到物理内存。映射由操作系统维护的页表数据结构在RAM中进行管理。地址转换逻辑(页表遍历)由CPU的内存管理单元(MMU)实现。MMU还有一个最近使用页面的缓存,即TLB。
当需要将虚拟地址转换为物理地址时,首先搜索TLB。如果找到匹配(TLB命中),则返回物理地址,内存访问可以继续。但是,如果没有找到匹配(称为TLB未命中),MMU通常会在页表中查找地址映射,看看是否存在映射。”页表遍历是昂贵的,因为它可能需要多次内存访问(尽管它们可能会命中CPU的L1/L2/L3缓存)。另一方面,TLB缓存大小有限,通常只能容纳几百个页面。
操作系统使用页面(连续的内存块)来管理虚拟内存。通常,一个内存页面的大小是4 KB。1 GB的内存是256,000个页面;128 GB是32,768,000个页面。显然,TLB缓存无法容纳所有页面,性能会因为缓存未命中而受到影响。有两种主要的改进方法。第一种是增加TLB大小,但这是昂贵的,而且对性能的帮助并不明显。另一种方法是增加页面大小,从而减少需要映射的页面数量。现代操作系统和CPU都支持大为2 MB甚至1 GB的页面。使用2 MB的大页面,128 GB的内存只需要64,000个页面。这就是为什么在Linux中有透明大页支持。这是一种优化!它自动和透明地为应用程序管理大页面。它的好处显而易见:应用程序无需进行任何更改;减少TLB未命中的次数;页表遍历变得更加廉价。
从更深层的原理出发,和前面说到的代价类似,使用透明大页也会有一些潜在的风险和开销,透明大页特性在逻辑上可以分为两个部分:分配和维护。
透明大页采用常规内存分配路径,并且需要操作系统能够找到连续且对齐的内存块。它会遇到和常规页面相同的问题,即碎片化。如果操作系统找不到连续的内存块,它将尝试压缩、回收或换出其他页面。这个过程很昂贵,并且可能导致延迟峰值(长达数秒)。这个问题在4.6kernel中得到了解决(通过“推迟”选项),如果操作系统无法分配大页面,它会回退到常规页面。
第二部分是维护。即使应用程序只访问了1字节的内存,它也会占用整个2MB的大页面。这显然是内存的浪费。因此,有一个后台内核线程叫做“khugepaged”。它扫描页面,试图对它们进行碎片整理和合并成一个大页面。尽管它是一个后台线程,但它会锁定处理的页面,因此也可能导致延迟峰值。另一个问题是大页面的分割,不是所有操作系统的部分功能都适用于大页面,比如交换。操作系统会将大页面拆分为常规页面。这可能导致性能下降和内存碎片化。
为什么要关闭透明大页
当透明大页开启时,操作系统会自动将内存页面大小变为2MB或更大,这意味着数据库内存将分配更多的空间,数据库处理大量的读写操作,而透明大页可能会导致内存的碎片化,而这可能会影响数据库的性能。当数据库使用大页时,它会将物理内存分割成固定大小的页,但是数据库的数据并不总是刚好填满整个页。这样就会造成内存浪费,使得操作系统需要花费额外的时间和资源来管理这些碎片化的内存空间。
举个例子,假设数据库有1GB的内存,而某个商品的数据只占用了10MB,那么使用透明大页时,数据库将分配1GB的内存给这个商品的数据。这就浪费了大量的内存资源。另外,关闭透明大页还有助于减少内存分页错误(page fault)的次数。在某些情况下,内存分页错误会导致数据库的性能下降,从而影响应用程序的响应时间。