首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

[linux][memory]hugetlb和hugepage技术分析

前言: 乍一看,hugetlb和hugepage还挺像的,好像都是所谓的“大页”。然而,却很难说出来它们的差异。作者也是花了写时间翻翻代码,写了几个测试的例子,加上用工具据实测了几个关键参数,才明白。 分析: 1,page fault 用户大多数情况下申请内存的方法: a,使用malloc函数族,其实是glibc封装了brk/mmap。这种情况下分配的是虚拟内存,并没有直接分配物理内存。 b,调用brk分配,这种情况很少见,并只分配虚拟内存。 c,使用mmap,分配出来虚拟内存。如果flags带有MAP_POPULATE,则直接分配物理内存,否则不分配物理内存。 d,使用mlock,则mlock指定的地址空间内,要分配物理内存。 上述的几种情况中,其实大多数情况下,都是只分配虚拟内存,并不分配物理内存。 对于这种情况,在CPU访问到没有分配物理内存的地址的时候,MMU会产生exception(这个exception就是page fault),kernel处理page fault,如果访问的地址合法,权限合法,则分配相应的物理内存。 2,2M page size 例如进程分配了4G的虚拟内存,那么,就会产生4G / 4K = 1M次page fault。 如果page size变大呢?page size为2M的情况下,会产生4G / 2M = 2K次page fault。 相比之下,减少了很多次page fault。换言之,就是节省了好多计算量,节省了处理page fault的CPU资源。 这里还有一个问题,为什么是2M,而不是其他呢?原因是在x86上,一个pmd能管理的内存是2M。 3,hugetlb shell中敲cat /proc/meminfo | grep -i HugePage:

可以看到,Hugepace的大小事2048K,即2M。当前没有HugePages的total,free等。 执行:echo 1024 > /proc/sys/vm/nr_hugepages

通过修改/proc/sys/vm/nr_hugepages,来控制kernel中的HugePages的数量。 在执行mmap函数中,flags带着MAP_HUGETLB则分配2M的page。 这里需要注意的是,HugePages不在buddy system中继续管理了,在修改echo 1024 > /proc/sys/vm/nr_hugepages的前后对比,也会发现,系统中的free memory减少了2G。 那么问题来了,如果buddy system中没有那么多连续的2M会怎么样呢?会分配出问题。所以需要在开机的时候,就分配大量的2M page。最差情况就是,2M page有很多剩余,而操作系统的free memory很少。 hugetlb的主要逻辑代码在linux-4.0.4/mm/hugetlb.c中,VM_HUGETLB的宏定义数值是0x00400000。 再需要说明一点,hugetlb标记的vma,是不能做ksm/uksm的。 4,hugepage linux提供函数int madvise(void *addr, size_t length, int advice); 其中advice有MADV_HUGEPAGE。 先看madvise,其实就是advise类型的,并非强制的。设定了MADV_HUGEPAGE的地址空间,可以在smaps中查到hg标记。 敲命令cat /proc/PID/smaps可以看到:

查看当前系统的hugepage的策略:cat /sys/kernel/mm/transparent_hugepage/enabled:

一共三种,always,madvise,never。修改的话,也同样:echo “always” > /sys/kernel/mm/transparent_hugepage/enabled 关于hugepage的代码,主要在linux-4.0.4/mm/huge_memory.c中实现。大意是,如果是hugepage的vma发生了page fault,则尽量使用2M的page来分配,如果分配失败,则使用普通的page(4K)来分配。同时,还有一个后台内核线程khugepaged,周期性扫描vma,如果kernel中有了2M的page,会尽量 使用2M page替换4K page。M_HUGEPAGE的宏定义是0x20000000。可见,和HUGETLB还是不同的。另外,hugepage是可以做ksm的。 5,test 作者写了一段简单的代码: #include <sys/mman.h> #include <sys/time.h> #include <stdio.h> #include <string.h> #define SIZE ((size_t)1024*1024*1024*1) int main(int argc, char *argv[]) { void *p = NULL; struct timeval starttv, endtv; int ret = 0; size_t index = 0; p = mmap(NULL, SIZE, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) { perror("mmap"); return 0; } if (argc == 2) { printf("madvise\n"); ret = madvise(p, SIZE, MADV_HUGEPAGE); if (ret < 0) { perror("madvise"); return 0; } } getchar(); ret = gettimeofday(&starttv, NULL); if (ret < 0) { perror("gettimeofday starttv"); return 0; } for (index = 0; index < SIZE; index += 4096) *((int*)(p + index)) = 0xc5; ret = gettimeofday(&endtv, NULL); if (ret < 0) { perror("gettimeofday starttv"); return 0; } printf("time cost : %ld\n", (endtv.tv_sec - starttv.tv_sec) * 1000000 + endtv.tv_usec - starttv.tv_usec); getchar(); return 0; } 先不要在意没有执行munmap的那段~: 编译:gcc hugepace.c -o hugepage 执行:a,./hugepage看执行时间 b, ./hugepage huge看执行时间

实验结果上来看,确实减少了执行时间。 6,perf 上面看到了执行时间的变少,再来看看具体的原因。 perf是性能分析的首选。 启动上述的测试程序hugepage,会停留在第一个getchar()位置上,这时候,敲:perf record -e page-faults -p PID;等到函数执行完,会在perf的shell里面生成perf.data文件;敲:perf report: 两次的结果分别是:

可见,page-faults的差别还是有很大差别。 7,/sys/kernel/mm/transparent_hugepage/enabled 前文提到过,这个参数有三个,当使用always的时候,其实kernel不在乎是否vma带有了VM_HUGEPAGE选项,都会优先使用2M page的。这里需要注意一下。 后记: 关于hugepage和hugetlb的代码,这里没有仔细分析,原因是代码比较繁杂,作者也只是大概看了逻辑,没完全看透。 从使用的角度来说,作者并不欣赏hugetlb,首先会划出去很多内存,让内存管理复杂化,诚然会让大块连续内存分配的情况下性能有所提高,不过看实验结果也没那么明显,还要牺牲挺多高级特性。而hugepage则好很多,非强制,hugepage使用的内存也是在buddy system的管理框架内。 网上有人说使用hugetlb会加速CPU访问内存的速度,作者并不清楚,也没想到一个好的办法来实验。期待有朋友给出来好办法或者数据。

下一篇
举报
领券