c - 使用 1GB 页面会降低性能

标签 c linux virtual-memory tlb

我有一个应用程序,我需要大约 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 倍!
我尝试使用 madviseMADV_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 页面(它有 psepdpe1gb 标志,参见: https://wiki.debian.org/Hugepages#x86_64 )。我测量的是机器时间,而不是 CPU 时间,我更新了代码,现在的结果是 25 次测试的平均值。
我还被告知这个测试在 2MB 页面上比普通 4KB 页面更好。

最佳答案

英特尔很友好地回复了这个问题。请参阅下面的答案。

此问题是由于实际提交物理页面的方式造成的。在 1GB 页的情况下,内存是连续的。因此,只要您写入 1GB 页面中的任何一个字节,就会分配整个 1GB 页面。但是,对于 4KB 页面,物理页面会在您第一次接触每个 4KB 页面时分配。

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;
   }
}
在最里面的循环中,索引以 512KB 的步幅变化。因此,连续引用映射在 512KB 偏移量处。通常缓存有 2048 个集合(即 2^11)。因此,位 6:16 选择集合。但是,如果您以 512KB 的偏移量步进,则位 6:16 将是相同的,最终选择相同的集合并丢失空间局部性。
我们建议在开始计时之前按如下顺序初始化整个 1GB 缓冲区(在小页面测试中)
for (uint64_t i = 0; i < size / sizeof(*ptr); i++)
    ptr[i] = i * 5;
基本上,问题在于由于非常大的恒定偏移量,在大页面与小页面相比的情况下,设置冲突会导致缓存未命中。当您使用恒定偏移量时,测试确实不是随机的。

关于c - 使用 1GB 页面会降低性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63705244/

相关文章:

PHP Mailer 在 Linux 中发送电子邮件太慢

operating-system - 当条目被从 TLB 驱逐时更新页表

c - 多线程进程的最大堆栈大小

c - 为什么我在第二次搜索文件时收到段错误错误?

c++ - 核心转储消息未在 STDERR 中捕获

html - 将 HTML 表单数据传递给 C 脚本和 CGI​​ 标准行为

linux - 您会为 Linux 推荐哪种甘特图/项目管理工具?

iPhone 应用程序在启动时使用 60MB 虚拟内存

c - 写入静态全局变量的访问冲突?

c++ - powerpc Maliit 框架交叉编译问题