首页 文章

了解在x86微基准测试中退役的装载和存储的数量

提问于
浏览
6

我最近一直在使用perf,我得到了一些我无法理解的结果 . 具体而言,数量或退役货物和商店与我的期望不符 .

我编写了一个非常简单的微基准测试代码,看看在一个非常简单的情况下结果是否有意义:

#include <stdio.h>

#define STREAM_ARRAY_SIZE   10000000

static double   a[STREAM_ARRAY_SIZE],
    b[STREAM_ARRAY_SIZE],
    c[STREAM_ARRAY_SIZE];


int main(){

    ssize_t j;

    for (j=0; j<STREAM_ARRAY_SIZE; j++) {
        a[j] = 1.0;
        b[j] = 2.0;
        c[j] = 0.0;
    }

    return 0;
}

我用gcc 4.6.3编译:

gcc -Wall -O benchmark.c -o benchmark

并且它确实编译为一个非常简单的程序集(用objdump -d获得)作为main:

00000000004004b4 <main>:
  4004b4:   b8 00 00 00 00          mov    $0x0,%eax
  4004b9:   48 be 00 00 00 00 00    movabs $0x3ff0000000000000,%rsi
  4004c0:   00 f0 3f
  4004c3:   48 b9 00 00 00 00 00    movabs $0x4000000000000000,%rcx
  4004ca:   00 00 40
  4004cd:   ba 00 00 00 00          mov    $0x0,%edx
  4004d2:   48 89 34 c5 40 10 60    mov    %rsi,0x601040(,%rax,8)
  4004d9:   00
  4004da:   48 89 0c c5 40 c4 24    mov    %rcx,0x524c440(,%rax,8)
  4004e1:   05
  4004e2:   48 89 14 c5 40 78 e9    mov    %rdx,0x9e97840(,%rax,8)
  4004e9:   09
  4004ea:   48 83 c0 01             add    $0x1,%rax
  4004ee:   48 3d 80 96 98 00       cmp    $0x989680,%rax
  4004f4:   75 dc                   jne    4004d2 <main+0x1e>
  4004f6:   b8 00 00 00 00          mov    $0x0,%eax
  4004fb:   c3                      retq
  4004fc:   90                      nop
  4004fd:   90                      nop
  4004fe:   90                      nop
  4004ff:   90                      nop

三个mov应该对应于存储器中的三个不同向量 . 我希望退役的商店数量非常接近30M而几乎没有负载因为我刚刚初始化了三个数组 . 然而,这是我在Sandy Bridge机器上得到的结果:

$ perf stat -e L1-dcache-loads,L1-dcache-stores ./benchmark 

 Performance counter stats for './benchmark':

        46,017,360 L1-dcache-loads                                             
        75,985,205 L1-dcache-stores

这是一台Nehalem机器:

$ perf stat -e L1-dcache-loads,L1-dcache-stores  ./benchmark

 Performance counter stats for './benchmark':                                                                                                                                                                                                

        45,255,731 L1-dcache-loads                                                                                                                                                                                                           
        60,164,676 L1-dcache-stores

退役的装载和存储如何计算每个针对内存的mov操作?为什么即使没有实际从内存中读取数据,怎么会有这么多的负载呢?

1 回答

  • 6

    所以我对此有点好奇并做了一些研究 . 主要是为了看看自上次使用它以来,perf框架有多么有用,它在共享开发机器上崩溃内核,另外25个开发人员对我的实验非常不满意 .

    首先,让我们验证一下你看到的内容:

    $ cc -O -o xx xx.c && perf stat -e L1-dcache-loads,L1-dcache-stores ./xx
    
     Performance counter stats for './xx':
    
            58,764,160 L1-dcache-loads
            81,640,635 L1-dcache-stores
    

    对 . 甚至更大的数字 . 那么发生了什么?让我们更好地记录和分析这个:

    $ cc -O -o xx xx.c && perf record -e L1-dcache-loads,L1-dcache-stores ./xx
    [... blah blah ...]
    $ perf report --stdio
    [... blah blah ...]
    # Samples: 688  of event 'L1-dcache-loads'
    # Event count (approx.): 56960661
    #
    # Overhead  Command      Shared Object    Symbol
    # ........  .......  .................  ........
    #
        95.80%       xx  [kernel.kallsyms]  [k] 0xffffffff811176ee
         4.20%       xx  xx                 [.] main
    
    
    # Samples: 656  of event 'L1-dcache-stores'
    # Event count (approx.): 80623804
    #
    # Overhead  Command      Shared Object    Symbol
    # ........  .......  .................  ........
    #
        61.72%       xx  [kernel.kallsyms]  [k] 0xffffffff811176ee
        38.28%       xx  xx                 [.] main
    

    啊哈,所以内核负责大多数负载和存储 . 我们得到的计数器计算内核和用户空间的缓存访问 .

    发生的事情是程序的物理页面(包括数据段和bss)在启动程序时没有映射甚至分配 . 当您第一次触摸它们时(或将来如果它们被分页),内核会将它们排除在外 . 我们可以看到:

    $ cc -O -o foo foo.c && perf stat -e faults ./xx
    
     Performance counter stats for './xx':
    
                58,696 faults
    

    我们实际上只是在一次运行期间执行58.7k页错误 . 由于页面大小为4096字节,我们得到 58696*4096=240418816 ,这大约是数组的240000000字节,其余的是程序,堆栈以及运行时所需的libc和ld.so中的各种垃圾 .

    所以现在我们可以弄清楚这些数字 . 让我们先看看商店,因为他们应该是最容易弄明白的 . 80623804*0.3828=30862792.1712 ,这样才有意义 . 我们预计有3000万家商店,我们有30.9家 . 由于性能计数器采样并且不完全准确,因此这是预期的 . 内核确实溢出的一些负载已经计入程序 . 在其他运行中,我获得的用户数不到30M .

    用户区以相同的方式获得2.4M负载 . 我怀疑这些实际上并不是在用户空间中加载,而是出于某种原因,一些访问内核在从陷阱中返回时会对您的程序进行处理 . 或类似的东西 . 我不确定那些,我不喜欢它们,但让我们看看我们是否可以消除噪音,并检查它与页面错误导致的垃圾数据有关的理论 .

    这是您的测试的更新版本:

    #include <stdio.h>
    
    #define STREAM_ARRAY_SIZE   10000000
    
    static double   a[STREAM_ARRAY_SIZE],
        b[STREAM_ARRAY_SIZE],
        c[STREAM_ARRAY_SIZE];
    
    void
    setup(void)
    {
        memset(a, 0, sizeof a);
        memset(b, 0, sizeof b);
        memset(c, 0, sizeof c);
    }
    
    void
    bench(void)
    {
        ssize_t j;
    
        for (j = 0; j < STREAM_ARRAY_SIZE; j++) {
            a[j] = 1.0;
            b[j] = 2.0;
            c[j] = 0.0;
        }
    }
    
    int
    main(int argc, char **argv)
    {
        setup();
        bench();
        return 0;
    }
    

    我确保在 setup 期间获得所有页面错误,然后在 bench 期间溢出的任何计数器应该具有非常小的内核噪声 .

    $ cc -O -o xx xx.c && perf record -e faults,L1-dcache-loads,L1-dcache-stores ./xx
    [...]
    $ perf report --stdio
    [...]
    # Samples: 468  of event 'faults'
    # Event count (approx.): 58768
    #
    # Overhead  Command      Shared Object             Symbol
    # ........  .......  .................  .................
    #
        99.20%       xx  libc-2.12.so       [.] __memset_sse2
         0.69%       xx  ld-2.12.so         [.] do_lookup_x
         0.08%       xx  ld-2.12.so         [.] dl_main
         0.02%       xx  ld-2.12.so         [.] _dl_start
         0.01%       xx  ld-2.12.so         [.] _start
         0.01%       xx  [kernel.kallsyms]  [k] 0xffffffff8128f75f
    
    
    # Samples: 770  of event 'L1-dcache-loads'
    # Event count (approx.): 61518838
    #
    # Overhead  Command      Shared Object             Symbol
    # ........  .......  .................  .................
    #
        96.14%       xx  [kernel.kallsyms]  [k] 0xffffffff811176ee
         3.86%       xx  libc-2.12.so       [.] __memset_sse2
    
    
    # Samples: 866  of event 'L1-dcache-stores'
    # Event count (approx.): 98243116
    #
    # Overhead  Command      Shared Object             Symbol
    # ........  .......  .................  .................
    #
        53.69%       xx  [kernel.kallsyms]  [k] 0xffffffff811176ee
        30.62%       xx  xx                 [.] bench
        15.69%       xx  libc-2.12.so       [.] __memset_sse2
    

    你有它 . 页面错误发生在调用 memset 期间,有些是在动态链接期间,以前主要的噪音现在发生在 memsetbench 本身没有任何负载和大约3000万个商店 . 就像我们预期的那样 . 这里有一个有趣的注意事项是 memset 知道如何在这台机器上有效率,并且只有一半的商店与你的测试相比,以填充相同数量的内存 . __memset_sse2 中的"sse2"是一个很好的提示 .

    我刚刚意识到有一件事可能不太清楚,我不会把它放在这里 . 性能计数器会对事件进行准确计数,但据我所知,如果您想知道这些事件发生的位置,CPU每次记录X事件时都只能生成一个陷阱 . 因此,工具不确定X是否至少为10000.因此,如果函数 bench 只触及堆栈一次并且发生了生成L1-dcache-load溢出陷阱,那么_2906756_函数将在 bench 函数中得到58593)也通过L1缓存解决,并将计入 . 所以无论你做什么,你都永远不会得到你期望的数字 .

相关问题