使用1GB页面会降低性能

时间:2020-09-02 12:10:53

标签: 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_SEQUENTIALhugepagesz=1G hugepages=1 default_hugepagesz=1G,但这没有帮助。

为什么在随机访问的情况下使用一个大页面会降低性能?通常,大页面(2MB和1GB)的用例是什么?

我没有在2MB的页面上测试此代码,我认为它应该做得更好。我还怀疑,由于1GB页存储在一个存储库中,因此它可能与multi-channels有关。但我想听听你们的消息。谢谢。

注意:要运行测试,必须首先在内核中启用1GB页面。您可以通过为内核提供此参数$ 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 来实现。更多:https://wiki.archlinux.org/index.php/Kernel_parameters。如果启用,您应该得到类似的信息:

pse

编辑1:我的机器具有Core i5 8600和4个内存块,每个内存块4 GB。 CPU本机支持2MB和1GB页面(它具有pdpe1gb和{{1}}标志,请参阅:https://wiki.debian.org/Hugepages#x86_64)。我当时在测量机器时间,而不是CPU时间,我更新了代码,现在的结果是25次测试的平均值。

我还被告知,此测试在2MB的页面上比普通的4KB的页面做得更好。

2 个答案:

答案 0 :(得分:3)

英特尔很乐意回答这个问题。请在下面查看他们的答案。


此问题归因于实际提交物理页面的方式。如果是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;

基本上,问题在于设置的冲突会导致高速缓存丢失,与大页面相比,大页面的情况是由于非常大的常量偏移量而导致的小页面。当您使用常量偏移量时,测试实际上不是随机

答案 1 :(得分:2)

不是答案,而是要为这个令人困惑的问题提供更多细节。

性能计数器显示的指令数量大致相似,但是使用大页面时所花费的周期数量大约是两倍:

  • 4KiB页面IPC 0.29,
  • 1GiB页面IPC 0.10。

这些IPC数字表示该代码在内存访问上处于瓶颈(Skylake上的CPU绑定IPC为3或更高)。巨大的页面瓶颈更加困难。

我修改了基准测试,在两种情况下都将MAP_POPULATE | MAP_LOCKED | MAP_FIXED与固定地址0x600000000000一起使用,以消除与页面错误和随机映射地址相关的时间变化。在我的Skylake系统上,2MiB和1GiB的速度比4kiB的速度慢2倍以上。

编译为g++-8.4.0 -std=gnu++14 -pthread -m{arch,tune}=skylake -O3 -DNDEBUG

[max@supernova:~/src/test] $ sudo hugeadm --pool-pages-min 2MB:64 --pool-pages-max 2MB:64
[max@supernova:~/src/test] $ sudo hugeadm --pool-pages-min 1GB:1 --pool-pages-max 1GB:1
[max@supernova:~/src/test] $ for s in small huge; do sudo chrt -f 40 taskset -c 7 perf stat -dd ./release/gcc/test $s random; done
Duration: 2156150

 Performance counter stats for './release/gcc/test small random':

       2291.190394      task-clock (msec)         #    1.000 CPUs utilized          
                 1      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                53      page-faults               #    0.023 K/sec                  
    11,448,252,551      cycles                    #    4.997 GHz                      (30.83%)
     3,268,573,978      instructions              #    0.29  insn per cycle           (38.55%)
       430,248,155      branches                  #  187.784 M/sec                    (38.55%)
           758,917      branch-misses             #    0.18% of all branches          (38.55%)
       224,593,751      L1-dcache-loads           #   98.025 M/sec                    (38.55%)
       561,979,341      L1-dcache-load-misses     #  250.22% of all L1-dcache hits    (38.44%)
       271,067,656      LLC-loads                 #  118.309 M/sec                    (30.73%)
           668,118      LLC-load-misses           #    0.25% of all LL-cache hits     (30.73%)
   <not supported>      L1-icache-loads                                             
           220,251      L1-icache-load-misses                                         (30.73%)
       286,864,314      dTLB-loads                #  125.203 M/sec                    (30.73%)
             6,314      dTLB-load-misses          #    0.00% of all dTLB cache hits   (30.73%)
                29      iTLB-loads                #    0.013 K/sec                    (30.73%)
             6,366      iTLB-load-misses          # 21951.72% of all iTLB cache hits  (30.73%)

       2.291300162 seconds time elapsed

Duration: 4349681

 Performance counter stats for './release/gcc/test huge random':

       4385.282466      task-clock (msec)         #    1.000 CPUs utilized          
                 1      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                53      page-faults               #    0.012 K/sec                  
    21,911,541,450      cycles                    #    4.997 GHz                      (30.70%)
     2,175,972,910      instructions              #    0.10  insn per cycle           (38.45%)
       274,356,392      branches                  #   62.563 M/sec                    (38.54%)
           560,941      branch-misses             #    0.20% of all branches          (38.63%)
         7,966,853      L1-dcache-loads           #    1.817 M/sec                    (38.70%)
       292,131,592      L1-dcache-load-misses     # 3666.84% of all L1-dcache hits    (38.65%)
            27,531      LLC-loads                 #    0.006 M/sec                    (30.81%)
            12,413      LLC-load-misses           #   45.09% of all LL-cache hits     (30.72%)
   <not supported>      L1-icache-loads                                             
           353,438      L1-icache-load-misses                                         (30.65%)
         7,252,590      dTLB-loads                #    1.654 M/sec                    (30.65%)
               440      dTLB-load-misses          #    0.01% of all dTLB cache hits   (30.65%)
               274      iTLB-loads                #    0.062 K/sec                    (30.65%)
             9,577      iTLB-load-misses          # 3495.26% of all iTLB cache hits   (30.65%)

       4.385392278 seconds time elapsed

在具有Intel i9-9900KS(不是NUMA)的Ubuntu 18.04.5 LTS上运行,所有4个插槽中都有4x8GiB 4GHz CL17 RAM,其中performance调速器用于无CPU频率缩放,最大为液冷风扇无热调节,FIFO 40优先级无抢占,在一个特定CPU内核上无CPU迁移,多次运行。结果与clang++-8.0.0编译器相似。

感觉硬件上有些混乱,例如每页帧的存储缓冲区,因此4KiB页每单位时间可增加约2倍的存储量。

看到AMD Ryzen 3 CPU的结果将很有趣。