超大内存的读写优化
随着技术的不断更新升级,不少服务器已经允许使用超大内存进行工作,实验室的编码服务器双CPU共提供了24个Channels,即376G的内存。但在使用过程中发现内存的访存时间波动非常大,于是查找了相关的优化方法,在此记录。
大页内存 - Hugepages
介绍
根据cybertan在他转载的文章hugepages中介绍(没有找到原出处)。下面的描述很清晰,具体查看与开启方法可以参考这篇博客。
就Linux应用程序而言,使用的都是虚拟地址,当应用程序读写一个指定的虚拟地址时,内存管理单元会自动进行虚拟地址到物理地址的转换。一个虚拟地址可以映射到多个物理地址,但当前映射到哪一个物理地址取决于当前的页表(Page Table,一个虚拟地址到物理地址的映射转换表)内容
页表存储在主存储器中,查询速度相对比较慢。为了提高地址转换性能,大多数体系架构都提供一个快速查找缓冲TLB(Translation Lookaside Buffer),TLB读写速度非常快,比如在X86体系架构上,TLB和普通的CPU CACHE并没有本质区别,只不过TLB专职用于缓存页表数据,而普通的CPU CACHE缓存实际的代码指令或数据。
TLB缓冲了最近使用过的页表项,在进行虚拟地址到物理地址的转换时先查这个TLB缓冲,只有当查找TLB缓冲失败(TLB miss)后才再去查普通页表(好像有的架构是同时进行查找?)。当然,根据局部性原理,大多数情况下应该都是查找TLB缓冲命中(TLB hit)的,所以性能得以大大提升。
关于大页内存的使用可以参考:Linux 大页面使用与实现简介 和 在 Linux on POWER 上利用透明大内存页 中的介绍。
安装
首先要确保相关的设置在内核中是允许的:
$ cat /usr/src/kernels/3.10.0-957.5.1.el7.x86_64/.config | grep HUGETLB
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_HUGETLBFS=y
CONFIG_HUGETLB_PAGE=y
这里我们选用libhugetlbfs
库
Cent OS
$ sudo yum install libhugetlbfs.x86_64
$ sudo yum install libhugetlbfs-devel.x86_64
使用libhugetlbfs
库的好处是简便,而且对内存映射的改变是透明的,即对程序是不可感知的,对程序的.bss
、.data
和malloc
获得的内存都将被分配到大页内存中
挂载
挂载映射到空目录:
$ su
# mkdir /libhugetlbfs
# groupadd libhuge
# chgrp libhuge /libhugetlbfs
# chmod 770 /libhugetlbfs
# usermod usr_name -G libhuge
# mount -t hugetlbfs hugetlbfs /libhugetlbfs
# exit
通过mount
挂载的设置只是暂时的,如果想要自动设置可以将信息添加进/etc/fstab
里,最后添加一行
1 /dev/sda3 / ext3 acl,user_xattr 1 1
2 /dev/sda2 swap swap defaults 0 0
3 proc /proc proc defaults 0 0
4 sysfs /sys sysfs noauto 0 0
5 debugfs /sys/kernel/debug debugfs noauto 0 0
6 devpts /dev/pts devpts mode=0620,gid=5 0 0
7 /dev/fd0 /media/floppy auto noauto,user,sync 0 0
8 hugetlbfs /libhugetlbfs hugetlbfs mode=0770,gid=1000 0 0
生效
$ sudo echo 0 > /proc/sys/vm/nr_hugepages
$ sudo echo 153600 > /proc/sys/vm/nr_hugepages # 300G
这里通过写入/proc/sys/vm/nr_hugepages
来控制大页内存的数量,第一次echo 0
是用来清零,为了更好的适应多CPU的分配,第二次echo
要根据自己需要内存的实际大小和页大小自行设置
分配完成后可以查看结果:
$ cat /proc/meminfo | grep Huge
AnonHugePages: 0 kB
HugePages_Total: 122880
HugePages_Free: 19556
HugePages_Rsvd: 10945
HugePages_Surp: 0
Hugepagesize: 2048 kB
$ cat /sys/devices/system/node/node*/meminfo | fgrep Huge
Node 0 AnonHugePages: 0 kB
Node 0 HugePages_Total: 61440
Node 0 HugePages_Free: 19554
Node 0 HugePages_Surp: 0
Node 1 AnonHugePages: 0 kB
Node 1 HugePages_Total: 61440
Node 1 HugePages_Free: 0
Node 1 HugePages_Surp: 0
第二种操作主要是看多CPU的情况下NUMA的分配,对页大小等其他参数的设置可以在内核启动之后加入:
$ sudo vi /etc/default/grub
>>> GRUB_CMDLINE_LINUX="crashkernel=auto transparent_hugepage=never default_hugepagesz=16M hugepagesz=16M hugepages=1526 rhgb quiet"
$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg
这里要注意grub/grub2哦
启动程序
只需要在启动程序之前加上libhugetlbfs
的库,让他帮你替换掉基础库就好啦
$ HUGETLB_MORECORE=yes LD_PRELOAD=libhugetlbfs.so ./some_app
mmap
这里有一篇比较好的研究文章,是Alan Hu写的认真分析mmap:是什么 为什么 怎么用。摘录其中几点:
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。