大页内存¶
- 大页内存通过增大单页粒度,减少页表遍历层级(1GB大页 4 级变 2 级),简化地址转换流程。
- 因少访问内存页表,单次地址转换延迟降低,避免 CPU 等待带来的性能损耗。
- 大幅提升 TLB 命中率,减少 TLB 未命中触发的页表遍历次数,尤其优化大内存场景的地址转换效率。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <unistd.h>
// --- 大页内存的不同实现方式 ---
// 1. 显式大页(EHP) + mmap(MAP_HUGETLB),高频交易中使用这种大页方式
// 原理: 在系统启动时通过内核参数 (vm.nr_hugepages 或 hugepages=N)
// 预留指定数量的大页。 应用程序通过 mmap() 配合 MAP_HUGETLB
// 标志来请求使用这些预留的大页。 程序中通过 mmap 使用大页内存(需链接
// -lhugetlbfs 库),本质是 mmap() 一个 hugetlbfs 上的文件。
// 高频交易中使用的方式,永久配置(避免重启失效):
// 方式1:编辑 /etc/sysctl.conf,添加配置后永久生效
// sudo vim /etc/sysctl.conf
// 新增行:vm.nr_hugepages = 10 (预留10个大页,数量需根据业务峰值调整)
// 执行 sudo sysctl -p 立即生效,重启后自动加载
// 方式2:若需独立管理配置,创建 /etc/sysctl.d/99-hugepages.conf
// sudo vim /etc/sysctl.d/99-hugepages.conf
// 新增行:vm.nr_hugepages = 10
// 执行 sudo sysctl --system 加载所有配置,重启后自动生效
// 优点: 性能最可预测,开销低,内存页在启动时就已分配,避免运行时碎片。
// 缺点: 需要root权限预配置,预留内存即使不使用也会占用物理内存。
void demonstrate_static_huge_pages() {
printf("\n--- Demonstrating Static Huge Pages with mmap(MAP_HUGETLB) ---\n");
size_t size = 2 * 1024 * 1024; // 2MB,通常为默认大页大小的倍数
void* huge_page_mem = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
if (huge_page_mem == MAP_FAILED) {
perror(
"mmap with MAP_HUGETLB failed. Make sure huge pages are configured "
"(e.g., add 'vm.nr_hugepages=1' to /etc/sysctl.conf and run 'sudo "
"sysctl -p') and available.");
return;
}
printf("Successfully allocated static huge page memory at %p\n",
huge_page_mem);
int* data = (int*)huge_page_mem;
data[0] = 123;
printf("Data at huge page: %d\n", data[0]);
if (munmap(huge_page_mem, size) == -1) {
perror("munmap failed");
}
printf("Successfully unmapped static huge page memory.\n");
}
// 2. 透明大页 (THP) + madvise(MADV_HUGEPAGE) (可选)
// 原理: 内核特性,尝试在后台自动将4KB小页合并为大页(通常2MB)。
// 应用程序无需明确请求,但可以通过 madvise() 提供提示以优化内核的行为。
// 高频交易关键配置(必须禁用THP,消除不确定性):
// 永久禁用THP(避免重启后内核自动启用):
// 方式1:编辑 /etc/sysctl.conf,添加禁用配置
// sudo vim /etc/sysctl.conf
// 新增行:vm.transparent_hugepage.enabled = never
// vm.transparent_hugepage.defrag = never (同时禁用碎片整理)
// 执行 sudo sysctl -p 立即生效
// 方式2:通过 grub 内核参数永久禁用(适用于需要完全屏蔽THP的场景)
// sudo vim /etc/default/grub
// 修改 GRUB_CMDLINE_LINUX 为:GRUB_CMDLINE_LINUX="transparent_hugepage=never"
// 执行 sudo update-grub 更新 grub 配置,重启系统后生效
// 优点: 对应用程序透明,无需代码修改即可受益。
// 缺点: 可能因内存碎片化难以分配,或导致性能抖动和内存膨胀,
// 此外,munmap、madvise等系统调用会影响内核必须在进程的组成线程之间通信的地址空间更改类型,造成TLB驱逐。
// 在高频交易中设置
// transparent_hugepage=never禁止内核在后台自动整理内存,消除不可预测的暂停。
void demonstrate_transparent_huge_pages() {
printf("\n--- Demonstrating Transparent Huge Pages (THP) ---\n");
// 分配普通内存,THP 会尝试将其转换为大页
size_t size = 4 * 1024 * 1024; // 4MB
void* normal_mem = malloc(size);
if (!normal_mem) {
perror("malloc failed");
return;
}
printf("Successfully allocated normal memory at %p\n", normal_mem);
// 可选:使用 madvise 提示内核尝试使用大页
// (仅当 /sys/kernel/mm/transparent_hugepage/enabled 设置为 madvise 或 always
// 时有效,高频场景中此配置已禁用)
if (madvise(normal_mem, size, MADV_HUGEPAGE) == -1) {
perror(
"madvise(MADV_HUGEPAGE) failed (THP is disabled in HFT scenario, set "
"'vm.transparent_hugepage.enabled=never' in /etc/sysctl.conf)");
// 应用程序可以继续使用普通内存
} else {
printf(
"madvise(MADV_HUGEPAGE) successful, kernel may convert to huge pages "
"(not recommended in HFT).\n");
}
// 像使用普通内存一样使用
char* data = (char*)normal_mem;
data[0] = 'A';
printf("Data in potentially THP-backed memory: %c\n", data[0]);
free(normal_mem);
printf("Successfully freed memory (potentially THP-backed).\n");
}
// 3. System V IPC 共享内存 + SHM_HUGETLB
// 原理: 使用 System V 共享内存接口 shmget() 结合 SHM_HUGETLB
// 标志来创建大页共享内存段。 依赖的大页永久配置(与显式大页一致):
// 需先按显式大页配置方式,在 /etc/sysctl.conf 或 /etc/sysctl.d/ 中设置
// vm.nr_hugepages=10(示例值),
// 确保系统启动时预留足够大页,避免运行时分配失败(高频场景不允许动态调整)
// 优点: 直接就是可以被多个进程挂载的匿名内存,随内核持续。
// 除非被显式删除 (ipcrm 或 shmctl with IPC_RMID) 或系统重启,
// 否则共享内存段会一直存在,即使创建它的进程已经退出。
// 缺点: 接口相对复杂,不如 mmap() 灵活。
void demonstrate_systemv_huge_pages() {
printf("\n--- Demonstrating System V IPC Huge Pages ---\n");
size_t size = 2 * 1024 * 1024; // 2MB
int shm_id;
void* shm_addr;
// 创建共享内存段,并指定 SHM_HUGETLB 标志
shm_id = shmget(IPC_PRIVATE, size, IPC_CREAT | SHM_R | SHM_W | SHM_HUGETLB);
if (shm_id == -1) {
perror(
"shmget with SHM_HUGETLB failed. Ensure huge pages are configured (add "
"'vm.nr_hugepages=10' to /etc/sysctl.conf and run 'sudo sysctl -p') "
"and available.");
return;
}
printf("Successfully created huge page shared memory with ID: %d\n", shm_id);
// 将共享内存附加到进程地址空间
shm_addr = shmat(shm_id, NULL, 0);
if (shm_addr == (void*)-1) {
perror("shmat failed");
shmctl(shm_id, IPC_RMID, NULL); // 清理
return;
}
printf("Successfully attached huge page shared memory at %p\n", shm_addr);
int* data = (int*)shm_addr;
data[0] = 456;
printf("Data in huge page shared memory: %d\n", data[0]);
// 分离共享内存
if (shmdt(shm_addr) == -1) {
perror("shmdt failed");
}
// 标记共享内存段为删除,当所有进程都分离后将被删除
if (shmctl(shm_id, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID failed");
}
printf("Successfully detached and marked shared memory for deletion.\n");
}
// 4. 大页文件系统 (Hugetlbfs)
// 原理: 专门用于支持大页的文件系统 (通常挂载在 /mnt/huge)。
// 高频交易永久配置(挂载+大页预留,确保重启后可用):
// 步骤1:永久预留大页(同显式大页配置)
// sudo vim /etc/sysctl.conf
// 新增:vm.nr_hugepages = 10 ,执行 sudo sysctl -p 生效
// 步骤2:永久挂载 hugetlbfs 到 /mnt/huge(避免重启后需重新挂载)
// sudo vim /etc/fstab
// 新增行:hugetlbfs /mnt/huge hugetlbfs defaults 0 0
// 执行 sudo mkdir -p /mnt/huge 创建目录,再执行 sudo mount /mnt/huge 立即挂载
// 重启后系统会自动从 /etc/fstab 加载挂载配置
// 此时 /mnt/huge 目录下创建的文件会直接关联到物理大页内存,无需临时挂载操作
// 优点: 可以通过文件路径来标识和访问大页,易于管理和共享。
// 缺点: 需要root权限挂载 hugetlbfs。
void demonstrate_hugetlbfs() {
printf("\n--- Demonstrating Hugetlbfs ---\n");
const char* filepath =
"/mnt/huge/my_huge_file"; // 依赖 /mnt/huge 永久挂载(配置在 /etc/fstab)
size_t size = 2 * 1024 * 1024; // 2MB
int fd = open(filepath, O_CREAT | O_RDWR, 0755);
if (fd == -1) {
perror(
"open hugetlbfs file failed. Make sure: 1. 'vm.nr_hugepages' is set in "
"/etc/sysctl.conf; 2. hugetlbfs is mounted permanently via /etc/fstab "
"(hugetlbfs /mnt/huge hugetlbfs defaults 0 0); 3. /mnt/huge is "
"writable.");
return;
}
if (ftruncate(fd, size) == -1) {
perror("ftruncate failed");
close(fd);
return;
}
void* huge_mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (huge_mem == MAP_FAILED) {
perror(
"mmap hugetlbfs file failed. Check if huge pages are reserved (via "
"/etc/sysctl.conf) and hugetlbfs is mounted.");
close(fd);
return;
}
printf("Successfully mapped huge page memory from file at %p\n", huge_mem);
int* data = (int*)huge_mem;
data[0] = 789;
printf("Data in huge page file mapped memory: %d\n", data[0]);
if (munmap(huge_mem, size) == -1) {
perror("munmap failed");
}
close(fd);
unlink(filepath); // 清理创建的文件
printf("Successfully unmapped and deleted hugetlbfs file.\n");
}
int main() {
// 运行前必须完成的永久配置:
// 1. 预留显式大页:在 /etc/sysctl.conf 或 /etc/sysctl.d/ 中设置
// vm.nr_hugepages=10,执行 sudo sysctl -p 生效
// 2. 禁用透明大页:在 /etc/sysctl.conf 中设置
// vm.transparent_hugepage.enabled=never,执行 sudo sysctl -p 生效
// 3. 永久挂载 hugetlbfs:在 /etc/fstab 中添加 hugetlbfs /mnt/huge hugetlbfs
// defaults 0 0,执行 sudo mount /mnt/huge 生效
demonstrate_static_huge_pages();
demonstrate_transparent_huge_pages();
demonstrate_systemv_huge_pages();
demonstrate_hugetlbfs();
printf("\n--- End of Huge Pages Demonstration ---\n");
return 0;
}
hugePageAllocator(非STL兼容),可直接改造依赖C风格数组的对象池的实现
#pragma once
#include <linux/mman.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#ifdef HUGE_PAGE_ALLOCATOR_DEBUG
#define HPA_DEBUG_PRINT(fmt, ...) \
fprintf(stderr, "[HugePageAllocator] " fmt, ##__VA_ARGS__)
#else
#define HPA_DEBUG_PRINT(fmt, ...) \
do { \
} while (0)
#endif
/**
* @brief 一个高性能的大页内存分配器
*
* 该分配器使用Linux的大页内存(mmap)来提供更高效的内存分配
*/
class HugePageAllocator {
public:
// 大页类型枚举
enum class HugePageSize {
DEFAULT, // 让系统自动选择
HUGETLB_2MB, // 2MB大页
HUGETLB_1GB // 1GB大页
};
/**
* @brief 分配大页内存
*
* @param size 请求的内存大小(字节)
* @param page_size 指定的大页大小类型
* @return 指向分配的内存的指针,如果分配失败则返回nullptr
*/
static inline void* allocate(
size_t size, HugePageSize page_size = HugePageSize::DEFAULT) noexcept {
// 检查请求的大小是否有效
if (size == 0) [[unlikely]] {
HPA_DEBUG_PRINT("Invalid size: 0\n");
return nullptr;
}
void* ptr = MAP_FAILED;
switch (page_size) {
case HugePageSize::HUGETLB_1GB: {
const size_t aligned_size =
(size + HPA_PAGE_SIZE_1GB - 1) & ~(HPA_PAGE_SIZE_1GB - 1);
ptr = mmap(nullptr, aligned_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_1GB, -1,
0);
if (ptr == MAP_FAILED) [[unlikely]] {
HPA_DEBUG_PRINT("1GB huge page allocation failed\n");
}
break;
}
case HugePageSize::HUGETLB_2MB:
case HugePageSize::DEFAULT: // 默认使用2MB大页
default: {
const size_t aligned_size =
(size + HPA_PAGE_SIZE_2MB - 1) & ~(HPA_PAGE_SIZE_2MB - 1);
ptr = mmap(nullptr, aligned_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_2MB, -1,
0);
if (ptr == MAP_FAILED) [[unlikely]] {
HPA_DEBUG_PRINT("2MB huge page allocation failed\n");
}
break;
}
}
// 如果指定的大页分配失败,则回退到普通页面
if (ptr == MAP_FAILED) [[unlikely]] {
const size_t page_size_bytes = (page_size == HugePageSize::HUGETLB_1GB)
? HPA_PAGE_SIZE_1GB
: HPA_PAGE_SIZE_2MB;
const size_t aligned_size =
(size + page_size_bytes - 1) & ~(page_size_bytes - 1);
ptr = mmap(nullptr, aligned_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) [[unlikely]] {
HPA_DEBUG_PRINT("Regular page allocation failed\n");
return nullptr;
}
}
return ptr;
}
/**
* @brief 释放之前分配的大页内存
*
* @param ptr 指向要释放的内存的指针
* @param size 要释放的内存大小(字节)
* @param page_size 分配时使用的大页大小类型
*/
static inline void deallocate(
void* ptr, size_t size,
HugePageSize page_size = HugePageSize::DEFAULT) noexcept {
if (ptr == nullptr) [[unlikely]] {
return;
}
const size_t page_size_bytes = (page_size == HugePageSize::HUGETLB_1GB)
? HPA_PAGE_SIZE_1GB
: HPA_PAGE_SIZE_2MB;
const size_t aligned_size =
(size + page_size_bytes - 1) & ~(page_size_bytes - 1);
if (munmap(ptr, aligned_size) != 0) [[unlikely]] {
HPA_DEBUG_PRINT("munmap failed\n");
}
}
/**
* @brief 获取系统页面大小
*
* @return 系统页面大小(字节)
*/
static inline size_t get_page_size() noexcept {
static const size_t page_size = []() noexcept -> size_t {
const long result = sysconf(_SC_PAGESIZE);
return (result != -1) ? static_cast<size_t>(result) : 4096UL;
}();
return page_size;
}
private:
static constexpr size_t HPA_PAGE_SIZE_2MB =
2ULL * 1024ULL * 1024ULL; // 2MB大页
static constexpr size_t HPA_PAGE_SIZE_1GB =
1ULL * 1024ULL * 1024ULL * 1024ULL; // 1GB大页
};