Linux之Hugepage
2019-12-11 22:48:51 阿炯

在 Linux 服务器中,内存是影响性能的主要因素之一,从内存管理的角度归纳了一些常见的影响因素(比如内存回收、Page Fault 增多、跨 NUMA 内存访问等),并介绍其对应的调优方法。

内存回收

操作系统总是会尽可能利用速度更快的存储介质来缓存速度更慢的存储介质中的内容,这样就可以显著的提高用户访问速度。比如文件一般都存储在磁盘上,磁盘对于程序运行的内存来说速度很慢,因此操作系统在读写文件时,都会将磁盘中的文件内容缓存到内存上(也叫做 page cache),这样下次再读取到相同内容时就可以直接从内存中读取,不需要再去访问速度更慢的磁盘,从而大大提高文件的读写效率。上述情况需要在内存资源充足的前提条件下,然而在内存资源紧缺时,操作系统自身难保,会选择尽可能回收这些缓存的内存,将其用到更重要的任务中去。这时候,如果用户再去访问这些文件,就需要访问磁盘,如果此时磁盘也很繁忙,那么用户就会感受到明显的卡顿,也就是性能变差了。

在 Linux 系统中,内存回收分为两个层面:整机和 memory cgroup。

在整机层面,设置了三条水线:min、low、high;当系统 free 内存降到 low 水线以下时,系统会唤醒 kswapd 线程进行异步内存回收,一直回收到 high 水线为止,这种情况不会阻塞正在进行内存分配的进程;但如果 free 内存降到了 min 水线以下,就需要阻塞内存分配进程进行回收,不然就有 OOM(out of memory)的风险,这种情况下被阻塞进程的内存分配延迟就会提高,从而感受到卡顿。


图 1. per-zone watermark

这些水线可以通过内核提供的 /proc/sys/vm/watermark_scale_factor 接口来进行调整,该接口 合法取值的范围为 [0, 1000],默认为 10,当该值设置为 1000 时,意味着 low 与 min 水线,以及 high 与 low 水线间的差值都为总内存的 10% (1000/10000)。

针对 page cache 型的业务场景,可以通过该接口抬高 low 水线,从而更早的唤醒 kswapd 来进行异步的内存回收,减少 free 内存降到 min 水线以下的概率,从而避免阻塞到业务进程,以保证影响业务的性能指标。在 memory cgroup 层面,目前内核没有设置水线的概念,当内存使用达到 memory cgroup 的内存限制后,会阻塞当前进程进行内存回收。不过内核在 v5.19 内核 中为 memory cgroup 提供了 memory.reclaim 接口,用户可以向该接口写入想要回收的内存大小,来提早触发 memory cgroup 进行内存回收,以避免阻塞 memory cgroup 中的进程。

Huge Page

内存作为宝贵的系统资源,一般都采用延迟分配的方式,应用程序第一次向分配的内存写入数据的时候会触发 Page Fault,此时才会真正的分配物理页,并将物理页帧填入页表,从而与虚拟地址建立映射。


图 2. Page Table

此后每次 CPU 访问内存,都需要通过 MMU 遍历页表将虚拟地址转换成物理地址。为了加速这一过程,一般都会使用 TLB(Translation-Lookaside Buffer)来缓存虚拟地址到物理地址的映射关系,只有 TLB cache miss 的时候,才会遍历页表进行查找。

页的默认大小一般为 4K, 随着应用程序越来越庞大,使用的内存越来越多,内存的分配与地址翻译对性能的影响越加明显。试想,每次访问新的 4K 页面都会触发 Page Fault,2M 的页面就需要触发 512 次才能完成分配。另外 TLB cache 的大小有限,过多的映射关系势必会产生 cacheline 的冲刷,被冲刷的虚拟地址下次访问时又会产生 TLB miss,又需要遍历页表才能获取物理地址。

对此,Linux 内核提供了大页机制。 上图的 4 级页表中,每个 PTE entry 映射的物理页就是 4K,如果采用 PMD entry 直接映射物理页,则一次 Page Fault 可以直接分配并映射 2M 的大页,并且只需要一个 TLB entry 即可存储这 2M 内存的映射关系,这样可以大幅提升内存分配与地址翻译的速度。

因此,一般推荐占用大内存应用程序使用大页机制分配内存。当然大页也会有弊端:比如初始化耗时高,进程内存占用可能变高等。可以使用 perf 工具对比进程使用大页前后的 PageFault 次数的变化:
perf stat -e page-faults -p <pid> -- sleep 5

目前内核提供了两种大页机制,一种是需要提前预留的静态大页形式,另一种是透明大页 (THP, Transparent Huge Page) 形式。

1、静态大页

首先来看静态大页,也叫做 HugeTLB。静态大页可以设置 cmdline 参数在系统启动阶段预留,比如指定大页 size 为 2M,一共预留 512 个这样的大页:
hugepagesz=2M hugepages=512

还可以在系统运行时动态预留,但该方式可能因为系统中没有足够的连续内存而预留失败。预留默认 size(可以通过 cmdline 参数 default_hugepagesz = 指定 size)的大页:
echo 20 > /proc/sys/vm/nr_hugepages

预留特定 size 的大页:
echo 5 > /sys/kernel/mm/hugepages/hugepages-*/nr_hugepages

预留特定 node 上的大页:
echo 5 > /sys/devices/system/node/node*/hugepages/hugepages-*/nr_hugepages

当预留的大页个数小于已存在的个数,则会释放多余大页(前提是未被使用) 。

编程中可以使用 mmap (MAP_HUGETLB) 申请内存。详细使用可以参考内核文档 。

这种大页的优点是一旦预留成功,就可以满足进程的分配请求,还避免该部分内存被回收;缺点是:

1.1.需要用户显式地指定预留的大小和数量

1.2.需要应用程序适配,比如:
mmap、shmget 时指定 MAP_HUGETLB;
挂载 hugetlbfs,然后 open 并 mmap

当然也可以使用开源 libhugetlbfs.so,这样无需修改应用程序。

1.3.预留太多大页内存后,free 内存大幅减少,容易触发系统内存回收甚至 OOM;紧急情况下可以手动减少 nr_hugepages,将未使用的大页释放回系统;也可以使用 v5.7 引入的 HugeTLB + CMA 方式,细节读者可以自行查阅。

2、透明大页

再来看透明大页,在 THP always 模式下,会在 Page Fault 过程中,为符合要求的 vma 尽量分配大页进行映射;如果此时分配大页失败,比如整机物理内存碎片化严重,无法分配出连续的大页内存,那么就会 fallback 到普通的 4K 进行映射,但会记录下该进程的地址空间 mm_struct;然后 THP 会在后台启动 khugepaged 线程,定期扫描这些记录的 mm_struct,并进行合页操作。因为此时可能已经能分配出大页内存了,那么就可以将此前 fallback 的 4K 小页映射转换为大页映射,以提高程序性能。整个过程完全不需要用户进程参与,对用户进程是透明的,因此称为透明大页。

虽然透明大页使用起来非常方便、智能,但也有一定的代价:
1)进程内存占用可能远大所需:因为每次 Page Fault 都尽量分配大页,即使此时应用程序只读写几 KB
2)可能造成性能抖动:
2.1.在第 1 种进程内存占用可能远大所需的情况下,可能造成系统 free 内存更少,更容易触发内存回收;系统内存也更容易碎片化。
2.2.khugepaged 线程合页时,容易触发页面规整甚至内存回收,该过程费时费力,容易造成 sys cpu 上升。
2.3.mmap lock 本身是目前内核的一个性能瓶颈,应当尽量避免 write lock 的持有,但 THP 合页等操作都会持有写锁,且耗时较长(数据拷贝等),容易激化 mmap lock 锁竞争,影响性能。

因此 THP 还支持 madvise 模式,该模式需要应用程序指定使用大页的地址范围,内核只对指定的地址范围做 THP 相关的操作。这样可以更加针对性、更加细致地优化特定应用程序的性能,又不至于造成反向的负面影响。可以通过 cmdline 参数和 sysfs 接口设置 THP 的模式:

cmdline 参数:
transparent_hugepage=madvise

sysfs 接口:
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

详细使用可以参考内核文档 。后文将展开详解。

mmap_lock 锁

该锁是内存管理中的一把知名的大锁,保护了诸如 mm_struct 结构体成员、 vm_area_struct 结构体成员、页表释放等很多变量与操作。

mmap_lock 的实现是读写信号量, 当写锁被持有时,所有的其他读锁与写锁路径都会被阻塞。Linux 内核已经尽可能减少了写锁的持有场景以及时间,但不少场景还是不可避免的需要持有写锁,比如 mmap 以及 munmap 路径、mremap 路径和 THP 转换大页映射路径等场景。

应用程序应该避免频繁的调用会持有 mmap_lock 写锁的系统调用 (syscall), 比如有时可以使用 madvise(MADV_DONTNEED)释放物理内存,该参数下,madvise 相比 munmap 只持有 mmap_lock 的读锁,并且只释放物理内存,不会释放 VMA 区域,因此可以再次访问对应的虚拟地址范围,而不需要重新调用 mmap 函数。

另外对于 MADV_DONTNEED,再次访问还是会触发 Page Fault 分配物理内存并填充页表,该操作也有一定的性能损耗。如果想进一步减少这部分损耗,可以改为 MADV_FREE 参数,该参数也只会持有 mmap_lock 的读锁,区别在于不会立刻释放物理内存,会等到内存紧张时才进行释放,如果在释放之前再次被访问则无需再次分配内存,进而提高内存访问速度。

一般 mmap_lock 锁竞争激烈会导致很多 D 状态进程(TASK_UNINTERRUPTIBLE),这些 D 进程都是进程组的其他线程在等待写锁释放。因此可以打印出所有 D 进程的调用栈,看是否有大量 mmap_lock 的等待。

for i in `ps -aux | grep " D" | awk '{ print $2}'`; do echo $i; cat /proc/$i/stack; done

内核社区专门封装了 mmap_lock 相关函数,并在其中增加了 tracepoint,这样可以使用 bpftrace 等工具统计持有写锁的进程、调用栈等,方便排查问题,确定优化方向。

bpftrace -e 'tracepoint:mmap_lock:mmap_lock_start_locking /args->write == true/{ @[comm, kstack] = count();}'

跨 numa 内存访问

在 NUMA 架构下,CPU 访问本地 node 内存的速度要大于远端 node,因此应用程序应尽可能访问本地 node 上的内存。可以通过 numastat 工具查看 node 间的内存分配情况:

观察整机是否有很多 other_node 指标(远端内存访问)上涨:
watch -n 1 numastat -s

查看单个进程在各个 node 上的内存分配情况:
numastat -p <pid>

1.绑 node

可以通过 numactl 等工具把进程绑定在某个 node 以及对应的 CPU 上,这样该进程只会从该本地 node 上分配内存。但这样做也有相应的弊端,比如:该 node 剩余内存不够时,进程也无法从其他 node 上分配内存,只能期待内存回收后释放足够的内存,而如果进入直接内存回收会阻塞内存分配,就会有一定的性能损耗。

此外,进程组的线程数较多时,如果都绑定在一个 node 的 CPU 上,可能会造成 CPU 瓶颈,该损耗可能比远端 node 内存访问还大,比如 ngnix 进程与网卡就推荐绑定在不同的 node 上,这样虽然网卡收包时分配的内存在远端 node 上,但减少了本地 node 的 CPU 上的网卡中断,反而可以获得更好的性能提升。

2.numa balancing

内核还提供了 numa balancing 机制,可以通过 /proc/sys/kernel/numa_balancing 文件或者 cmdline 参数 numa_balancing = 进行开启。

该机制可以动态的将进程访问的 page 从远端 node 迁移到本地 node 上,从而使进程可以尽可能的访问本地内存。但该机制实现也有相应的代价, 在 page 的迁移是通过 Page Fault 机制实现的,会有相应的性能损耗;另外如果迁移时找不到合适的目标 node,可能还会把进程迁移到正在访问的 page 的 node 的 CPU 上,这可能还会导致 cpu cache miss,从而对性能造成更大的影响。因此需要根据业务进程的具体行为,来决定是否开启 numa balancing 功能。


接上文,来对Hugepage再展开进一步的详述。


对于这个大内存的管理(物理内存+虚拟内存),大多数操作系统采用了分段或分页的方式进行管理。分段是粗粒度的管理方式,而分页则是细粒度管理方式,分页方式可以避免内存空间的浪费。相应地,也就存在内存的物理地址与虚拟地址的概念。通过上面这两种方式,CPU必须把虚拟地址转换程物理内存地址才能真正访问内存。为了提高这个转换效率,CPU会缓存最近的虚拟内存地址和物理内存地址的映射关系,并保存在一个由CPU维护的映射表中。为了尽量提高内存的访问速度,需要在映射表中保存尽量多的映射关系。

Linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会按照LRU算法在适当的时候将物理内存中不经常使用的内存页自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。通常情况下,Linux默认情况下每页是4K,这就意味着如果物理内存很大,则映射表的条目将会非常多,会影响CPU的检索效率。因为内存大小是固定的,为了减少映射表的条目,可采取的办法只有增加页的尺寸,因此Hugepage便因此而来。也就是打破传统的小页面的内存管理方式,使用大页面2M,4M等。如此一来映射条目则明显减少,TLB 缓存命中率将大大提高。

HugePages是通过使用大页内存来取代传统的4kb内存页面,使得管理虚拟地址数变少,加快了从虚拟地址到物理地址的映射以及通过摒弃内存页面的换入换出以提高内存的整体性能。尤其是对于8GB以上的内存以及较大的Oracle SGA size,建议配值并使用HugePage特性。下文基于x86_64 Linux下来描述如何配值 HugePages。
一般来说操作系统分配内存的最小单元是页,一般是 4KB 大小,但是这个页放到现在来说可能有点“不够用”,因为很多程序内存消耗很大,分配内存很频繁,所以选择更大的页可以提升性能,大页带来的好处很多,首先是页表的层次可以减少,增加访存的速度,其次是减少 TLB miss 的概率,同时 page fault 也会减少,减少到 hugepage size / 4KB (x86_64 一般有 2MB 的 hugepage 和 1GB 的 hugepage)。


HugePages 有两种类型,一种是 THP(Transparent Huge Page) ,顾名思义,就是对用户来说对这种大页是无感知的,它本身可以被分成 4KB 的小页,并且可以被 swap out,有一个 Khugepaged 周期性扫描 4KB 的页合并成大页。另一种大页是 persistent hugepage,这种 page 是预先分配的并且不能拆分成 4KB 小页,而且不能 swap out;这种的隐患是可能在内存 fragmentation 太多分不出大页的时候压缩小页,这在内存分配有压力的时候会造成很大的性能影响。

使用 persistent hugepage 可以通过 echo 512 | tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages,这会预先分配 512 个 2MB 的大页。具体使用是通过 fs/hugetlbfs 下实现的hugetlbfs来实现的,应用程序需要通过mmap进行文件映射来使用这些大页。


HugePages相关概念

Page Table: page table也就是一种用于内存管理的实现方式,用于物理地址到虚拟之间的映射。因此对于内存的访问,先是访问Page Table,然后根据Page Table 中的映射关系,隐式的转移到物理地址来存取数据。

TLB: Translation Lookaside Buffer (TLB) ,CPU中的一块固定大小的cache,包含了部分page table的映射关系,用于快速实现虚拟地址到物理地址的转换。

hugetlb: hugetlb 是TLB中指向HugePage的一个entry(通常大于4k或预定义页面大小)。 HugePage 通过hugetlb entries来实现,也可以理解为HugePage 是hugetlb page entry的一个句柄。

hugetlbfs: 一个类似于tmpfs的新的in-memory filesystem,在2.6内核被提出。

x86(包括x86-32和x86-64)架构的CPU默认使用4KB大小的内存页面(getconf PAGESIZE),但是它们也支持较大的内存页,如x86-64系统就支持2MB大小的大页(huge page)。传统的内存页面大小是4KB,linux 2.6 引入了Hugepage属性,即内存页的大小可以从2M到1G,不同的CPU硬件对Hugepage的大小支持不一样,比如:ia64 architecture supports multiple page sizes 4K, 8K, 64K, 256K, 1M, 4M, 16M, 256M and ppc64 supports 4K and 16M。

如果在系统中使用了huge page,则内存页的数量会减少,从而需要更少的页表(page table),节约了页表所占用的内存数量,并且所需的地址转换也减少了,TLB缓存失效的次数就减少了,从而提高了内存访问的性能。另外,由于地址转换所需的信息一般保存在CPU的缓存中,huge page的使用让地址转换信息减少,从而减少了CPU缓存的使用,减轻了CPU缓存的压力,让CPU缓存能更多地用于应用程序的数据缓存,也能够在整体上提升系统的性能。

在系统启动期间,能用大内存页为应用程序预留一部分内存。这部分内存,即被大内存页占用的这些存储器永远不会被交换出内存,它会一直保留其中,除非修改了配置。这会极大地提高像 Oracle 数据库这样的需要海量内存的应用程序的性能。

大页的主要优点:使用大页可以减少页表项数量,从而减少TLB Miss的概率,提升系统访存性能。当然有利必有弊,使用大页降低了内存管理的粒度和灵活性,如果程序并不是对内存的使用量特别大,使用大页还可能造成内存的浪费。对于内存访问密集型的应用,在KVM客户机中使用huge page是可以较明显地提高客户机性能的,不过它也有一个缺点,使用huge page的内存不能被换出(swap out),也不能使用ballooning方式自动增长。

操作系统对于数据的存取直接从物理内存要比从磁盘读写数据要快的多,但是物理内存是有限的,这样就引出了物理内存与虚拟内存的概念。虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,这部分磁盘空间Windows下称之为虚拟内存,Linux下被称为交换空间(Swap Space)。


为什么使用大内存页

在虚拟内存管理中,内核维护一个将虚拟内存地址映射到物理地址的表,对于每个页面操作,内核都需要加载相关的映射。如果你的内存页很小,那么你需要加载的页就会很多,导致内核会加载更多的映射表。而这会降低性能。使用大内存页,意味着所需要的页变少了,从而大大减少由内核加载的映射表的数量,提高了内核级别的性能最终有利于应用程序的性能。简而言之,通过启用大内存页,系统具只需要处理较少的页面映射表,从而减少访问与维护的开销!

Linux系统中,对程序可使用的内存地址是Virtual Address。每个程序的内存地址都是从0开始的。而实际的数据访问是要通过Physical Address进行的。因此,每次内存操作,CPU都需要从page table中把Virtual Address翻译成对应的Physical Address,那么对于大量内存密集型程序来说page table的查找就会成为程序的瓶颈。所以现代CPU中就出现了TLB(Translation Lookaside Buffer) Cache用于缓存少量热点内存地址的mapping关系。然而由于制造成本和工艺的限制,响应时间需要控制在CPU Cycle级别的Cache容量只能存储几十个对象。那么TLB Cache在应对大量热点数据Virual Address转换的时候就显得捉襟见肘了。按照标准的Linux页大小(page size) 4K,一个能缓存64元素的TLB Cache只能涵盖4K*64 = 256K的热点数据的内存地址,显然距理想非常遥远的,于是HugePage就产生了。

HugePage既然改变不了TLB Cache的容量,那么只能从系统层面增加一个TLB Cache entry所能对应的物理内存大小,从而增加TLB Cache所能涵盖的热点内存数据量。假设我们把Linux Page Size增加到16M,那么同样一个容纳64个元素的TLB Cache就能顾及64*16M = 1G的内存热点数据,这样的大小相较上文的256K就显得非常适合实际应用了。像这种将Page Size加大的技术就是HugePage,这种设计理念就是增加一层软件调用来解决现有的问题。

注意:不要把Virutal Address和Windows上的虚拟内存搞混了,后者是为了应对物理内存不足,而将内容从内存换出到其他设备的技术(类似于Linux的SWAP机制)。

HugePage优点

对于较大的系统内存以及sga,使用hugepage可以极大程度的提高Oracle数据库性能。

a、Not swappable
无需交换。也就是不存在页面由于内存空间不足而存在换入换出的问题。

b、Relief of TLB pressure
减轻TLB的压力,也就是降低了cpu cache可缓存的地址映射压力。由于使用了huge page,相同的内存大小情况下,管理的虚拟地址数量变少。TLB entry可以包含更多的地址空间,cpu的寻址能力相应的得到了增强。

c、Decreased page table overhead
降低page table负载,对于普通的page,每个entry需要64bytes进行管理,对于50gb的内存,管理这些entry,需要800mb的大小:
(50*1024*1024)kb/4kb*64bytes/1024/1024=800mb。

d、Eliminated page table lookup overhead
消除page table查找负载。

e、Faster overall memory performance
提高内存的整体性能。

运行下面命令来查看当前大内存页的详细内容。

# grep Huge /proc/meminfo
AnonHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

从上面输出可以看到,每个页的大小为 2MB(Hugepagesize),并且系统中目前有 0 个“大内存页”(HugePages_Total)。这里“大内存页”的大小可以从 2MB 增加到 1GB。


Regular Pages 与 HugePages

a、Regular Pages

在下图中有两个不同的进程,两个进程对于内存的访问是首先访问本地的page table,而本地的page table又参照了system-wide table的page(也就是TLB),最终system-wide table中的entry指向了实际的物理地址。图中物理地址page size大小4kb。也可以看到进程1和进程2在system-wide table中都指向了page2,也就是同一个物理地址。Oracle Sga中共享内存的使用会出现上述情形。



b、Huge Pages

在下图中,本地的page table与system page table中都包含了huge page属性。因此page table中的任意一个page可能使用了常规的page,也有可能使用了huge page。同样进程1和进程2都共享了其中的Hpage2。图中的物理内存常规的page size是4kb,huge page size 是4mb。



HugePage与NUMA

HugePage在NUMA环境的各种不同应用场景下带来的性能差异。它对于相当一部分的应用场景并不能很好的提升性能,甚至会带来高达10%的性能损耗。性能下降的原因主要有以下两点:

1. CPU对同一个Page抢占增多

对于写操作密集型的应用,HugePage会大大增加Cache写冲突的发生概率。由于CPU独立Cache部分的写一致性用的是MESI协议,写冲突就意味:
通过CPU间的总线进行通讯,造成总线繁忙
同时也降低了CPU执行效率。
CPU本地Cache频繁失效

类比到数据库就相当于,原来需要锁定10行数据,现在需要锁定1000行了,这必然这把锁在线程之间的争抢概率大大增加。

2. 连续数据需要跨CPU读取(False Sharing)

原本在4K小页上可以连续分配,并因为较高命中率而在同一个CPU上实现locality的数据。到了HugePage的情况下,就有一部分数据为了填充统一程序中上次内存分配留下的空间,而被迫分布在了两个页上。而在所在HugePage中占比较小的那部分数据,由于在计算CPU亲和力的时候权重小,自然就被附着到了其它CPU上。那么就会造成:本该以热点形式存在于CPU2 L1或者L2 Cache上的数据,不得不通过CPU inter-connect去remote CPU获取数据。


配置内核中的“大内存页”选项

本文最后一部分内容是配置上面提到的 内核参数 ,然后重新加载。将下面内容添加到 /etc/sysctl.conf 中,然后输入 sysctl -p 命令重新加载配置。
vm.nr_hugepages=126

注意我们这里多加了两个额外的页,因为我们希望在实际需要的页面数量之外多一些额外的空闲页。现在,内核已经配置好了,但是要让应用能够使用这些“大内存页”还需要提高内存的使用阀值。新的内存阀值应该为 126 个页 x 每个页 2 MB = 252 MB,也就是 258048 KB。

你需要编辑 /etc/security/limits.conf 中的如下配置:
soft memlock 258048
hard memlock 258048

某些情况下,这些设置是在指定应用的文件中配置的,比如 Oracle就是在 /etc/security/limits.d/99-grid-oracle-limits.conf 中配置的。这样就完成了!可能还需要重启应用来让应用来使用这些新的巨大页。

如何使用hugepage

一般来说有三种方法:
使用mmap+MAP_HUGETLB直接匿名映射
使用shmget+SHM_HUGETLB属性分配
mount hugetlbfs后,在hugetlbfs下创建文件,然后通过mmap进行映射

注:上述方法使用的前提是,系统中加载了hugetlbfs,且存在空闲大页。可以通过cat /proc/meminfo查看是否有空闲内存。另外本质上匿名大页也是有对应的大页文件系统中的匿名文件,其实并不是真正的匿名页。


一些相关的外文参考

Hugepages is a mechanism that allows the Linux kernel to utilize the multiple page size capabilities of modern hardware architectures. Linux uses pages as the basic unit of memory, where physical memory is partitioned and accessed using the basic page unit. The default page size is 4096 Bytes in the x86 architecture.Hugepages allows large amounts of memory to be utilized with a reduced overhead. Linux uses "Transaction Lookaside Buffers" (TLB) in the CPU architecture. These buffers contain mappings of virtual memory to actual physical memory addresses. So utilizing a huge amount of physical memory with the default page size consumes the TLB and adds processing overhead.
 
The Linux kernel is able to set aside a portion of physical memory to be able be addressed using a larger page size. Since the page size is higher, there will be less overhead managing the pages with the TLB. In the Linux 2.6 series of kernels, hugepages is enabled using the CONFIG_HUGETLB_PAGE feature when the kernel is built. Systems with large amount of memory can be configured to utilize the memory more efficiently by setting aside a portion dedicated for hugepages. The actual size of the page is dependent on the system architecture.

A typical x86 system will have a Huge Page Size of 2048 kBytes. The huge page size may be found by looking at the /proc/meminfo:
# cat /proc/meminfo |grep Hugepagesize

Number of Hugepages can be allocated using the /proc/sys/vm/nr_hugepages entry, or by using the sysctl command.
To view the current setting using the /proc entry:
# cat /proc/sys/vm/nr_hugepages

To view the current setting using the sysctl command:
# sysctl vm.nr_hugepages

To set the number of huge pages using /proc entry:
# echo 5 > /proc/sys/vm/nr_hugepages

To set the number of hugepages using sysctl:
# sysctl -w vm.nr_hugepages=5

It may be necessary to reboot to be able to allocate the number of hugepages that is needed. This is because hugepages requires large areas of contiguous physical memory. Over time, physical memory may be mapped and allocated to pages, thus the physical memory can become fragmented. If the hugepages are allocated early in the boot process, fragmentation is unlikely to have occurred.

It is recommended that the /etc/sysctl.conf file should be used to allocate hugepages at boot time. For example, to allocate 5 hugepages at boot time add the line below to the sysctl.conf file :
vm.nr_hugepages = 5


参考来源

NUMA for Linux
Linux hugepage 大页内存理论
HugePage是否是拯救性能的万能良药