目录
文章目录
前文列表
大页内存
在页式虚拟存储器中,会在虚拟存储空间和物理主存空间都分割为一个个固定大小的页,为线程分配内存是也是以页为单位。比如:页的大小为 4K,那么 4GB 存储空间就需要 4GB/4KB=1M 条记录,即有 100 多万个 4KB 的页。我们可以相待,如果页太小了,那么就会产生大量的页表条目,降低了查询速度的同时还浪费了存放页面的主存空间;但如果页太大了,又会容易造成浪费,原因就跟段式存储管理方式一般。所以 Linux 操作系统默认的页大小就是 4KB,可以通过指令查看:
$ getconf PAGE_SIZE
4096
但在某些对性能要求非常苛刻的场景中,页面会被设置得非常的大,比如:1GB、甚至几十 GB,这些页被称之为 “大页”(Huge Page)。大页能够提升性能的主要原因有以下几点:
- 减少页表条目,加快检索速度。
- 提升 TLB 快表的命中率,TLB 一般拥有 16 ~ 128 个条目之间,也就是说当大页为 1GB 的时候,TLB 能够对应 16GB ~ 128GB 之间的存储空间。
值得注意的是,首先使用大页的同时一般会禁止主存-辅存页面交换(Swap),原因跟段式存储管理方式一样,大容量交换会让辅存读写成为 CPU 处理的瓶颈。 虽然现今在数据中心闪存化的环境中,这个问题得到了缓解,但代价就是昂贵的 SSD 存储设备。再一个就是大页也会使得页内地址检索的速度变慢,所以并非是页面的容量越大越好,而是需要对应用程序进行大量的测试取得页面容量与性能的曲线峰值才对。
启用 HugePage 的优点:
- 无需交换,不存在页面由于内存空间不足而换入换出的问题。
- 减轻 TLB Cache 的压力,也就是降低了 CPU Cache 可缓存的地址映射压力。
- 降低 Page Table 的负载。
- 消除 Page Table 地查找负载。
- 提高内存的整体性能。
启用 HugePage 的缺点:
- HugePages 会在系统启动时,直接分配并保留对应大小的内存区域
- HugePages 在开机之后,如果没有管理员的介入,是不会释放和改变的。
Linux 的大页内存
在 Linux 中,物理内存是以页为单位来管理的。默认的,页的大小为 4KB。 1MB 的内存能划分为 256 页; 1GB 则等同于 256000 页。 CPU 中有一个内置的内存管理单元(MMU),用于存储这些页的列表(页表),每页都有一个对应的入口地址。4KB 大小的页面在 “分页机制” 提出的时候是合理的,因为当时的内存大小不过几十兆字节。然而,当前计算机的物理内存容量已经增长到 GB 甚至 TB 级别了,操作系统仍然以 4KB 大小为页面的基本单位的话,会导致 CPU 中 MMU 的页面空间不足以存放所有的地址条目,则会造成内存的浪费。
同时,在 Linux 操作系统上运行内存需求量较大的应用程序时,采用的默认的 4KB 页面,将会产生较多 TLB Miss 和缺页中断,从而大大影响应用程序的性能。当操作系统以 2MB 甚至更大作为分页的单位时,将会大大减少 TLB Miss 和缺页中断的数量,显著提高应用程序的性能。
为了解决上述问题,自 Linux Kernel 2.6 起,引入了 Huge pages(巨型页)的概念,目的是通过使用大页内存来取代传统的 4KB 内存页面, 以适应越来越大的内存空间。Huge pages 有 2MB 和 1GB 两种规格,2MB 大小(默认)适合用于 GB 级别的内存,而 1GB 大小适合用于 TB 级别的内存。
大页的实现原理
为了能以最小的代价实现大页面支持,Linux 采用了 hugetlb 和 hugetlbfs 两个概念。其中,hugetlb 是记录在 TLB 中的条目并指向 hugepages,而 hugetlbfs 则是一个特殊文件系统(本质是内存文件系统)。hugetlbfs 主要的作用是使得应用程序可以根据需要灵活地选择虚拟存储器页面的大小,而不会全局性的强制使用某个大小的页面。在 TLB 中通过 hugetlb 来指向 hugepages,可以通过 hugetlb entries 来调用 hugepages,这些被分配的 hugepages 再以 hugetlbfs 内存文件系统的形式提供给进程使用。
-
Regular Page 的分配:当一个进程请求内存时,它需要访问 PageTable 去调用一个实际的物理内存地址,继而获得内存空间。
-
Huge Page 的分配:当系统配置 Huge pages 后,进程依然通过普通的 PageTable 来获取到实际的物理内存地址。但不同的是,在 Process PageTable 和 System PageTable 第增加了 Hugepage(HPage)属性。
可见,进程当需要使用 Huge pages 时,只需要声明 Hugepage 属性,让系统分配 PageTable 中的 Huge pages 条目即可实现。所以,实际上 Regular page 和 Huge page 是共享一个 PageTable 的,这就是所谓的以最小的代码来支持 Huge pages。
使用 Huge pages 的好处是很明显的,假设应用程序需要 2MB 的内存,如果操作系统以 4KB 作为分页的单位,则需要 512 个页面,进而在 TLB 中需要 512 个表项,同时也需要 512 个页表项,操作系统需要经历至少 512 次 TLB Miss 和 512 次缺页中断才能将 2MB 应用程序空间全部映射到物理内存;然而,当操作系统采用 2MB 作为分页的基本单位时,只需要一次 TLB Miss 和一次缺页中断,就可以为 2MB 的应用程序空间建立虚实映射,并在运行过程中无需再经历 TLB Miss 和缺页中断(假设未发生 TLB 项替换和 Swap)。
此外,使用 Huge pages 还能减少系统管理和处理器访问内存页的时间(扩大了 TLB 快页表查询的内存地址范围),Linux 内核中的 Swap(内存交换)守护进程也不会管理大页面占用的这部分空间。合理设置大页面能减少内存操作的负担,减少访问页表造成性能瓶颈的可能性,从而提升系统性能。由此,如果你的系统经常碰到因为 Swap 而引发的性能问题,或者你的计算机内存空间非常大时,都可以考虑启用大页内存。
大页内存配置
大页面配置需要连续的内存空间,因此在开机时就分配是最可靠的设置方式。配置大页面的参数有:
-
hugepages :在内核中定义了开机启动时就分配的永久大页面的数量。默认为 0,即不分配。只有当系统有足够的连续可用页时,分配才会成功。由该参数保留的页不能用于其他用途。
-
hugepagesz: 在内核中定义了开机启动时分配的大页面的大小。可选值为 2MB 和 1GB 。默认是 2MB 。
-
default_hugepagesz:在内核中定义了开机启动时分配的大页面的默认大小。
-
Step 1. 查看 Linux 操作系统是否启动了大页内存,如果 HugePages_Total 为 0,意味着 Linux 没有设置或没有启用 Huge pages。
$ grep -i HugePages_Total /proc/meminfo
HugePages_Total: 0
- Step 2. 查看是否挂载了 hugetlbfs
$ mount | grep hugetlbfs
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime)
- Step 3. 如果没有挂载则手动挂载
$ mkdir /mnt/huge_1GB
$ mount -t hugetlbfs nodev /mnt/huge_1GB
$ vim /etc/fstab
nodev /mnt/huge_1GB hugetlbfs pagesize=1GB 0 0
- Step 4. 修改 grub2,例如为系统配置 10 个 1GB 的大页面
$ vim /etc/grub2.cfg
# 定位到 linux16 /vmlinuz-3.10.0-327.el7.x86_64 在行末追加
default_hugepagesz=1G hugepagesz=1G hugepages=10
NOTE:配置大页面后,系统在开机启动时会首选尝试在内存中找到并预留连续的大小为 hugepages * hugepagesz
的内存空间。如果内存空间不满足,则启动会报错 Kernel Panic, Out of Memory
等错误。
- Step 5. 重启系统,查看更详细的大页内存信息
$ cat /proc/meminfo | grep -i Huge
AnonHugePages: 1433600 kB # 匿名 HugePages 数量
HugePages_Total: 0 # 分配的大页面数量
HugePages_Free: 0 # 没有被使用过的大页面数量
HugePages_Rsvd: 0 # 已经被分配预留但是还没有使用的大页面数目,应该尽量保持 HugePages_Free - HugePages_Rsvd = 0
HugePages_Surp: 0 # surplus 的缩写,表示大页内存池中大于 /proc/sys/vm/nr_hugepages 中值的大页面数量
Hugepagesize: 1048576 kB # 每个大页面的 Size,与 HugePages_Total 的相乘得到大页面池的总容量
如果大页面的 Size 一致,则可以通过 /proc/meminfo
中的 Hugepagesize
和 HugePages_Total
计算出大页面所占内存空间的大小。这部分空间会被算到已用的内存空间里,即使还未真正被使用。因此,用户可能观察到下面现象:使用 free
命令查看已用内存很大,但 top
或者 ps
中看到 %mem
的使用总量加起来却很少。
- Step 6. 如果上述输出看见 Hugepagesize 已经设置成 1GB,但 HugePages_Total 还是为 0,那么需要修改内核参数设定大页面的数量
$ sysctl -w vm.nr_hugepages=10
# 或者
$ echo 'vm.nr_hugepages = 10' > /etc/sysctl.conf
$ sysctl -p
NOTE:一般情况下,配置的大页面可能主要供特定的应用程序或服务使用,其他进程是无法共享这部分空间的(如 Oracle SGA)。 请根据系统物理内存和应用需求来设置合适的大小,避免大页面使用的浪费;以及造成其他进程因竞争剩余可用内存而出现内存溢出的错误,进而导致系统崩溃的现象。默认的,当存在大页面时,会在应用进程或者内核进程申请大页内存的时候,优先为它们分配大页面,大页面不足以分配时,才会分配传统的 4KB 页面。查看哪个程序在使用大页内存:
grep -e AnonHugePages /proc/*/smaps | awk '{if(2>4)print0}' | awk -F "/" '{print0;system("ps−fp"3)}'
透明巨型页 THP
Transparent Huge pages(THP,透明大页) 自 RHEL 6 开始引入。由于传统的 Huge pages 很难手动的管理,对于程序而言,可能需要修改很多的代码才能有效的使用。THP 的引入就是为了便于系统管理员和开发人员使用大页内存。THP 是一个抽象层,能够自动创建、管理和使用传统大页。操作系统将大页内存看作是一种系统资源,在 THP 开启的情况下,其他的进程也可以申请和释放大页内存。
Huge pages 和 Transparent Huge pages 在大页内存的使用方式上存在区别,前者是预分配的方式,而后者则是动态分配的方式,显然后者更适合程序使用。需要注意的是,THP 虽然方便,但在某些场景种仍然会建议我们关闭,这个需要结合实际应用场景慎重考虑。
手动关闭 THP:
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled
$ echo never > /sys/kernel/mm/transparent_hugepage/defrag
$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
# - [always] 表示启用了 THP
# - [never] 表示禁用了 THP
# - [madvise] 表示只在 MADV_HUGEPAGE 标志的 VMA 中使用 THP
永久关闭 THP:
vim /etc/grub2.cfg
# 在 cmdline 追加:
transparent_hugepage=never
大页面对内存的影响
需要注意的是,为大页面分配的内存空间会被计算到已用内存空间中,即使它们还未真正被使用。因此,你可能观察到下面现象:使用 free 命令查看已用内存很大,但 top 或者 ps 指令中看到 %MEM 的使用总量加起来却很少。
例如:总内存为 32G,并且分配了 12G 大页面的 free 如下
[root@localhost ~]# free -g
total used free shared buff/cache available
Mem: 31 16 14 0 0 14
Swap: 3 0 3
命令 top 输出, Shift+m 按内存使用量进行排序:
命令 ps -eo uid,pid,rss,trs,pmem,stat,cmd
,查看进程的内存使用量:
这种情况就导致了一个问题,如果盲目的去提高大页内存空间的占比,就很可能会出现胖的胖死,饿的饿死的问题。导致大页内存空间的浪费,因为普通程序是未必能够使用大页内存的。
大页内存的性能问题
在页式虚拟存储器中,会在虚拟存储空间和物理主存空间都分割为一个个固定大小的页,为线程分配内存是也是以页为单位。比如:页的大小为 4K,那么 4GB 存储空间就需要 4GB/4KB=1M 条记录,即有 100 多万个 4KB 的页。我们可以相待,如果页太小了,那么就会产生大量的页表条目,降低了查询速度的同时还浪费了存放页面的主存空间;但如果页太大了,又会容易造成浪费,原因就跟段式存储管理方式一般。所以 Linux 操作系统默认的页大小就是 4KB,可以通过指令查看:
$ getconf PAGE_SIZE
4096
但在某些对性能要求非常苛刻的场景中,页面会被设置得非常的大,比如:1GB、甚至几十 GB,这些页被称之为 “大页”(Huge Page)。大页能够提升性能的主要原因有以下几点:
- 减少页表条目,加快检索速度。
- 提升 TLB 快表的命中率,TLB 一般拥有 16 ~ 128 个条目之间,也就是说当大页为 1GB 的时候,TLB 能够对应 16GB ~ 128GB 之间的存储空间。
值得注意的是,首先使用大页的同时一般会禁止主存-辅存页面交换,原因跟段式存储管理方式一样,大容量交换会让辅存读写成为 CPU 处理的瓶颈。再一个就是大页也会使得页内地址检索的速度变慢,所以并非是页面的容量越大越好,而是需要对应用程序进行大量的测试取得页面容量与性能的曲线峰值才对。