您现在的位置是:首页 >技术教程 >Linux下使用HugePage减少TLB-Miss,以及使用HugePage创建共享内存网站首页技术教程

Linux下使用HugePage减少TLB-Miss,以及使用HugePage创建共享内存

WilliamCode 2023-06-28 20:00:03
简介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
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。