【发布时间】:2020-12-21 14:32:32
【问题描述】:
我有一个应用程序,我需要大约 850 MB 的连续内存并以随机方式访问它。有人建议我分配一个 1 GB 的大页面,以便它始终位于 TLB 中。我编写了一个带有顺序/随机访问的演示,以测量小(在我的情况下为 4 KB)与大(1 GB)页面的性能:
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#define MAP_HUGE_2MB (21 << MAP_HUGE_SHIFT) // Aren't used in this example.
#define MAP_HUGE_1GB (30 << MAP_HUGE_SHIFT)
#define MESSINESS_LEVEL 512 // Poisons caches if LRU policy is used.
#define RUN_TESTS 25
void print_usage() {
printf("Usage: ./program small|huge1gb sequential|random\n");
}
int main(int argc, char *argv[]) {
if (argc != 3 && argc != 4) {
print_usage();
return -1;
}
uint64_t size = 1UL * 1024 * 1024 * 1024; // 1GB
uint32_t *ptr;
if (strcmp(argv[1], "small") == 0) {
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, // basically malloc(size);
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap small");
exit(1);
}
} else if (strcmp(argv[1], "huge1gb") == 0) {
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_1GB, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap huge1gb");
exit(1);
}
} else {
print_usage();
return -1;
}
clock_t start_time, end_time;
start_time = clock();
if (strcmp(argv[2], "sequential") == 0) {
for (int iter = 0; iter < RUN_TESTS; iter++) {
for (uint64_t i = 0; i < size / sizeof(*ptr); i++)
ptr[i] = i * 5;
}
} else if (strcmp(argv[2], "random") == 0) {
// pseudorandom access pattern, defeats caches.
uint64_t index;
for (int iter = 0; iter < RUN_TESTS; iter++) {
for (uint64_t i = 0; i < size / MESSINESS_LEVEL / sizeof(*ptr); i++) {
for (uint64_t j = 0; j < MESSINESS_LEVEL; j++) {
index = i + j * size / MESSINESS_LEVEL / sizeof(*ptr);
ptr[index] = index * 5;
}
}
}
} else {
print_usage();
return -1;
}
end_time = clock();
long double duration = (long double)(end_time - start_time) / CLOCKS_PER_SEC;
printf("Avr. Duration per test: %Lf\n", duration / RUN_TESTS);
// write(1, ptr, size); // Dumps memory content (1GB to stdout).
}
在我的机器上(更多如下)结果是:
顺序:
$ ./test small sequential
Avr. Duration per test: 0.562386
$ ./test huge1gb sequential <--- slightly better
Avr. Duration per test: 0.543532
随机:
$ ./test small random <--- better
Avr. Duration per test: 2.911480
$ ./test huge1gb random
Avr. Duration per test: 6.461034
我对随机测试感到困扰,似乎 1GB 的页面慢了 2 倍!
我尝试使用 madvise 和 MADV_SEQUENTIAL / MADV_SEQUENTIAL 进行各自的测试,但没有帮助。
为什么在随机访问的情况下使用一个巨大的页面会降低性能?大页面(2MB 和 1GB)的一般用例是什么?
我没有用 2MB 页面测试这段代码,我认为它应该会做得更好。我还怀疑,由于一个 1GB 页面存储在一个内存库中,它可能与 multi-channels 有关。但我想听听你们的意见。谢谢。
注意:要运行测试,您必须首先在内核中启用 1GB 页面。你可以通过给内核这个参数hugepagesz=1G hugepages=1 default_hugepagesz=1G来做到这一点。更多:https://wiki.archlinux.org/index.php/Kernel_parameters。如果启用,你应该得到类似的东西:
$ cat /proc/meminfo | grep Huge
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 1
HugePages_Free: 1
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 1048576 kB
Hugetlb: 1048576 kB
EDIT1:我的机器有 Core i5 8600 和 4 个内存库,每个 4 GB。 CPU 本身支持 2MB 和 1GB 页面(它具有 pse 和 pdpe1gb 标志,请参阅:https://wiki.debian.org/Hugepages#x86_64)。我测量的是机器时间,而不是 CPU 时间,我更新了代码,结果现在是 25 次测试的平均值。
我还被告知,这个测试在 2MB 页面上比在普通 4KB 页面上表现更好。
【问题讨论】:
-
你脱离了上下文。连续的虚拟地址空间在物理地址空间中是不连续的。如果你认为分配一块内存会减少页面错误从而提高性能,那么在系统中,结果通常是反直觉的。
-
@TonyTannous Huge pages - 如果支持 - 在物理内存中是连续的
-
难道你不应该同时使用
MAP_POPULATE和MAP_LOCKED,除非你想专门测试故障性能?无论如何,您应该可以使用perf来查看 TLB、缓存和其他硬件计数器。 -
@TonyTannous 据我所知,一个虚拟页面,如果我们在我的情况下谈论内存映射(但它也可能是文件映射/设备/等),对应于一个物理页面具有确切大小或具有该大小的连续内存块。 x86_64 ISA 支持 2MB 和 1GB 页面:wiki.debian.org/Hugepages#x86_64。
-
我确认您的观察,1GB 页面随机访问比 Skylake 上的 4kB 页面慢两倍。很奇特。
标签: c linux virtual-memory tlb