您现在的位置是:首页 >技术教程 >Linux下使用HugePage减少TLB-Miss,以及使用HugePage创建共享内存网站首页技术教程
Linux下使用HugePage减少TLB-Miss,以及使用HugePage创建共享内存
个人作品,禁止转载
概述
TLB miss是导致程序内存访问延迟的一大原因。特别是使用大量内存时TLB会变得异常拥挤。一个常见的TLB miss导致的内存延迟的现象是:访问一个连续数组,每4KB出现一次较大的访问延迟。
因此使用HugePage(巨页)能够改善内存访问延迟。
本文主要介绍在共享内存IPC场景下使用HugePage的方法。单进程访问内存的方式会更简单。
HugePage的内核支持
see also:https://docs.kernel.org/admin-guide/mm/hugetlbpage.html
内核支持两种HugePage使用方式:Transparent Huge Pages 和 Explicit huge pages
see also:https://stackoverflow.com/questions/64913818/hugepages-on-raspberry-pi-4/64914391#64914391
Transparent Huge Pages由操作系统管理,允许用户将已经mmap的地址转换为HugePage,也允许系统层面使用HugePage,可以将所有内存申请自动映射到HugePage上,无需修改代码。
Explicit huge pages允许用户控制HugePage的分配、使用和销毁。
在系统中预留HugePage
关于如何在启动阶段或者启动后创建HugePage,参考https://docs.kernel.org/admin-guide/mm/hugetlbpage.html
使用HugePage backed Shared Memory
x64架构支持两种巨页大小:2M和1G
假设用户已经使用
mount -t hugetlbfs none /mnt/hugetlbfs_1G -o pagesize=1G
mount -t hugetlbfs none /mnt/hugetlbfs_2M -o pagesize=2M
将hugetlbfs挂载到/mnt目录下,并授予了当前用户读写权限
创建巨页文件,并映射到共享内存
现在hugetlbfs中创建一个文件,并truncate到巨页的大小
int fd = open(("/mnt/hugetlbfs_2M/test" + to_string(num)).c_str(), O_CREAT | O_RDWR | O_TRUNC, 0766);
if (fd == -1) {
perror("open");
exit(-1);
}
ftruncate(fd, 2UL * 1024 * 1024);
这个文件会存放在巨页内存中,该内存大小为2M
下面将文件映射到进程的地址空间
void* addr = mmap(nullptr, FSize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_HUGETLB, fd, 0);
if (addr == MAP_FAILED){
perror("mmap");
exit(-1);
}
然后就可以使用addr
访问巨页了
创建1GB的巨页,也相同。
完整代码
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
static const size_t FSize = 2UL * 1024 * 1024;
int map_file()
{
// create file under hugetlbfs
int fd = open(("/mnt/hugetlbfs_2M/test" + to_string(1)).c_str(), O_CREAT | O_RDWR | O_TRUNC, 0766);
if (fd == -1) {
perror("open");
exit(-1);
}
ftruncate(fd, FSize);
void* addr = mmap(nullptr, FSize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_HUGETLB, fd, 0);
if (addr == MAP_FAILED){
perror("mmap");
exit(-1);
}
int* arr = (int*)addr;
for (size_t i=0; i<FSize/sizeof(int); i++)
arr[i] = i;
munmap(addr, FSize);
return 0;
}
int main(){
map_file();
}
运行一次改代码之后,在命令行运行
cat /proc/meminfo | grep Huge
会输出
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 1024
HugePages_Free: 1023
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 4194304 kB
即一个2M的巨页已经被使用。
然后运行rm -f /mnt/hugetlbfs_2M/*
再度运行cat /proc/meminfo | grep Huge
会输出
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 1024
HugePages_Free: 1024
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 4194304 kB
2M HugePage已经被删除(巨页内存被释放)
巨页内存耗尽
如果一直在hugetlbfs中创建巨页,却不删除文件(即释放内存),会将巨页耗尽。
如下代码模拟了这种场景(系统中预留1024个2M巨页)
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
static const size_t FSize = 2UL * 1024 * 1024;
int map_file(int num)
{
cout << "call for " << num << " times" << endl;
// create file under hugetlbfs
int fd = open(("/mnt/hugetlbfs_2M/test" + to_string(num)).c_str(), O_CREAT | O_RDWR | O_TRUNC, 0766);
if (fd == -1) {
perror("open");
exit(-1);
}
ftruncate(fd, FSize);
void* addr = mmap(nullptr, FSize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_HUGETLB, fd, 0);
if (addr == MAP_FAILED){
perror("mmap");
exit(-1);
}
int* arr = (int*)addr;
for (size_t i=0; i<FSize/sizeof(int); i++)
arr[i] = i % 26 + 'a';
munmap(addr, FSize);
return 0;
}
int main(){
for (int i=0; ; i++)
map_file(i);
}
程序输出为
call for 0 times
call for 1 times
......
call for 1021 times
call for 1022 times
call for 1023 times
call for 1024 times
mmap: Cannot allocate memory
Process finished with exit code 255