Performance 测量内存访问时间x86

Performance 测量内存访问时间x86,performance,caching,assembly,memory,x86,Performance,Caching,Assembly,Memory,X86,我试图测量缓存/非缓存内存访问时间,结果让我感到困惑 代码如下: 1 #include <stdio.h> 2 #include <x86intrin.h> 3 #include <stdint.h>

我试图测量缓存/非缓存内存访问时间,结果让我感到困惑

代码如下:

  1 #include <stdio.h>                                                              
  2 #include <x86intrin.h>                                                          
  3 #include <stdint.h>                                                             
  4                                                                                 
  5 #define SIZE 32*1024                                                            
  6                                                                                 
  7 char arr[SIZE];                                                                 
  8                                                                                 
  9 int main()                                                                      
 10 {                                                                               
 11     char *addr;                                                                 
 12     unsigned int dummy;                                                         
 13     uint64_t tsc1, tsc2;                                                        
 14     unsigned i;                                                                 
 15     volatile char val;                                                          
 16                                                                                 
 17     memset(arr, 0x0, SIZE);                                                     
 18     for (addr = arr; addr < arr + SIZE; addr += 64) {                           
 19         _mm_clflush((void *) addr);                                             
 20     }                                                                           
 21     asm volatile("sfence\n\t"                                                   
 22             :                                                                   
 23             :                                                                   
 24             : "memory");                                                        
 25                                                                                 
 26     tsc1 = __rdtscp(&dummy);                                                    
 27     for (i = 0; i < SIZE; i++) {                                                
 28         asm volatile (                                                          
 29                 "mov %0, %%al\n\t"  // load data                                
 30                 :                                                               
 31                 : "m" (arr[i])                                                  
 32                 );                                                              
 33                                                                                 
 34     }                                                                           
 35     tsc2 = __rdtscp(&dummy);                                                    
 36     printf("(1) tsc: %llu\n", tsc2 - tsc1);                                     
 37                                                                                 
 38     tsc1 = __rdtscp(&dummy);                                                    
 39     for (i = 0; i < SIZE; i++) {                                                
 40         asm volatile (                                                          
 41                 "mov %0, %%al\n\t"  // load data                                
 42                 :                                                               
 43                 : "m" (arr[i])                                                  
 44                 );                                                              
 45                                                                                 
 46     }                                                                           
 47     tsc2 = __rdtscp(&dummy);                                                    
 48     printf("(2) tsc: %llu\n", tsc2 - tsc1);                                     
 49                                                                                 
 50     return 0;                                                                   
 51 } 
我预计,第一个值会大得多,因为在案例(1)中,clflush使缓存失效

有关我的cpu(英特尔(R)核心(TM)i7 cpu Q 720@1.60GHz)缓存的信息:

两条rdtscp指令之间的代码反汇编

  400614:       0f 01 f9                rdtscp 
  400617:       89 ce                   mov    %ecx,%esi
  400619:       48 8b 4d d8             mov    -0x28(%rbp),%rcx
  40061d:       89 31                   mov    %esi,(%rcx)
  40061f:       48 c1 e2 20             shl    $0x20,%rdx
  400623:       48 09 d0                or     %rdx,%rax
  400626:       48 89 45 c0             mov    %rax,-0x40(%rbp)
  40062a:       c7 45 b4 00 00 00 00    movl   $0x0,-0x4c(%rbp)
  400631:       eb 0d                   jmp    400640 <main+0x8a>
  400633:       8b 45 b4                mov    -0x4c(%rbp),%eax
  400636:       8a 80 80 10 60 00       mov    0x601080(%rax),%al
  40063c:       83 45 b4 01             addl   $0x1,-0x4c(%rbp)
  400640:       81 7d b4 ff 7f 00 00    cmpl   $0x7fff,-0x4c(%rbp)
  400647:       76 ea                   jbe    400633 <main+0x7d>
  400649:       48 8d 45 b0             lea    -0x50(%rbp),%rax
  40064d:       48 89 45 e0             mov    %rax,-0x20(%rbp)
  400651:       0f 01 f9                rdtscp
400614:0f 01 f9 rdtscp
400617:89 ce mov%ecx,%esi
400619:48 8b 4d d8 mov-0x28(%rbp),%rcx
40061d:89 31 mov%esi,(%rcx)
40061f:48 c1 e2 20 shl$0x20,%rdx
400623:48 09 d0或%rdx,%rax
400626:48 89 45 c0 mov%rax,-0x40(%rbp)
40062a:c7 45 b4 00动产$0x0,-0x4c(%rbp)
400631:eb 0d jmp 400640
400633:8b 45 b4 mov-0x4c(%rbp),%eax
400636:8a 80 80 10 60 00 mov 0x601080(%rax),%al
40063c:83 45 b4 01地址$0x1,-0x4c(%rbp)
400640:81 7d b4 ff 7f 00 cmpl$0x7fff,-0x4c(%rbp)
400647:76 ea jbe 400633
400649:48 8d 45 b0 lea-0x50(%rbp),%rax
40064d:48 89 45 e0 mov%rax,-0x20(%rbp)
400651:0f 01 f9 rdtscp

看来我遗漏了什么/误解了什么。您可以建议吗?

mov%0、%%al
速度太慢(每64个时钟有一条缓存线),您可能会在这方面遇到瓶颈,无论您的负载最终是来自DRAM还是L1D

只有每64次加载都会在缓存中丢失,因为您通过微小的字节加载循环充分利用了空间局部性。如果您真的想测试刷新L1D大小的块后缓存重新填充的速度,那么应该使用SIMD
movdqa
循环,或者只使用跨距为64的字节加载。(每个缓存线只需触摸一个字节)

为了避免对RAX旧值的错误依赖,应该使用
movzbl%0,%eax
。这将允许Sandybridge和更高版本(或自K8以来的AMD)使用每个时钟2个负载的满负载吞吐量来保持内存管道接近满负载。多个缓存未命中可能会同时发生:英特尔CPU核心有10个LFB(行填充缓冲区)用于与L1D之间的行,或16个超级队列条目用于从L2到非核心的行。另见。(许多核心Xeon芯片的单线程内存带宽比台式机/笔记本电脑差。)


但你的瓶颈远比这糟糕

您在编译时禁用了优化功能,因此循环使用
addl$0x1,-0x4c(%rbp)
作为循环计数器,这将为您提供至少6个循环的循环携带依赖链。(ALU添加的存储/重新加载存储转发延迟+1个周期。)

(可能更高,因为加载端口的资源冲突。i7-720是Nehalem微体系结构,因此只有一个加载端口。)

这肯定意味着您的循环不会因缓存未命中而出现瓶颈,并且无论您是否使用
clflush
都可能以相同的速度运行

还要注意,
rdtsc
统计参考周期,而不是核心时钟周期。i、 e.无论CPU运行速度慢(省电)还是快(Turbo),它在1.7GHz的CPU上的计数始终为1.7GHz。通过预热回路对此进行控制


您也没有在
eax
上声明clobber,因此编译器不希望您的代码修改
rax
。最终得到的是
mov 0x601080(%rax),%al
。但是gcc每次迭代都从内存中重新加载
rax
,并且不使用您修改的
rax
,因此您实际上不会像使用优化编译时那样在内存中跳转


提示:如果您想让编译器实际加载,而不是将其优化为更少更宽的加载,请使用
volatile char*
。对此,您不需要内联asm。

mov%0、%%al
非常慢(每64个时钟有一条缓存线),因此无论您的负载最终是否来自DRAM或L1D,您都可能会在这方面遇到瓶颈

只有每64次加载都会在缓存中丢失,因为您通过微小的字节加载循环充分利用了空间局部性。如果您真的想测试刷新L1D大小的块后缓存重新填充的速度,那么应该使用SIMD
movdqa
循环,或者只使用跨距为64的字节加载。(每个缓存线只需触摸一个字节)

为了避免对RAX旧值的错误依赖,应该使用
movzbl%0,%eax
。这将允许Sandybridge和更高版本(或自K8以来的AMD)使用每个时钟2个负载的满负载吞吐量来保持内存管道接近满负载。多个缓存未命中可能会同时发生:英特尔CPU核心有10个LFB(行填充缓冲区)用于与L1D之间的行,或16个超级队列条目用于从L2到非核心的行。另见。(许多核心Xeon芯片的单线程内存带宽比台式机/笔记本电脑差。)


但你的瓶颈远比这糟糕

您在编译时禁用了优化功能,因此循环使用
addl$0x1,-0x4c(%rbp)
作为循环计数器,这将为您提供至少6个循环的循环携带依赖链。(ALU添加的存储/重新加载存储转发延迟+1个周期。)

(可能更高,因为加载端口的资源冲突。i7-720是Nehalem微体系结构,因此只有一个加载端口。)

这肯定意味着您的循环不会因缓存未命中而出现瓶颈,并且无论您是否使用
clflush
都可能以相同的速度运行

还要注意,
rdtsc
统计参考周期,而不是核心时钟周期。i、 e.在您的1.7GHz CPU上,它将始终计数为1.7GHz,re
Cache ID 0:
- Level: 1
- Type: Data Cache
- Sets: 64
- System Coherency Line Size: 64 bytes
- Physical Line partitions: 1
- Ways of associativity: 8
- Total Size: 32768 bytes (32 kb)
- Is fully associative: false
- Is Self Initializing: true

Cache ID 1:
- Level: 1
- Type: Instruction Cache
- Sets: 128
- System Coherency Line Size: 64 bytes
- Physical Line partitions: 1
- Ways of associativity: 4
- Total Size: 32768 bytes (32 kb)
- Is fully associative: false
- Is Self Initializing: true

Cache ID 2:
- Level: 2
- Type: Unified Cache
- Sets: 512
- System Coherency Line Size: 64 bytes
- Physical Line partitions: 1
- Ways of associativity: 8
- Total Size: 262144 bytes (256 kb)
- Is fully associative: false
- Is Self Initializing: true

Cache ID 3:
- Level: 3
- Type: Unified Cache
- Sets: 8192
- System Coherency Line Size: 64 bytes
- Physical Line partitions: 1
- Ways of associativity: 12
- Total Size: 6291456 bytes (6144 kb)
- Is fully associative: false
- Is Self Initializing: true
  400614:       0f 01 f9                rdtscp 
  400617:       89 ce                   mov    %ecx,%esi
  400619:       48 8b 4d d8             mov    -0x28(%rbp),%rcx
  40061d:       89 31                   mov    %esi,(%rcx)
  40061f:       48 c1 e2 20             shl    $0x20,%rdx
  400623:       48 09 d0                or     %rdx,%rax
  400626:       48 89 45 c0             mov    %rax,-0x40(%rbp)
  40062a:       c7 45 b4 00 00 00 00    movl   $0x0,-0x4c(%rbp)
  400631:       eb 0d                   jmp    400640 <main+0x8a>
  400633:       8b 45 b4                mov    -0x4c(%rbp),%eax
  400636:       8a 80 80 10 60 00       mov    0x601080(%rax),%al
  40063c:       83 45 b4 01             addl   $0x1,-0x4c(%rbp)
  400640:       81 7d b4 ff 7f 00 00    cmpl   $0x7fff,-0x4c(%rbp)
  400647:       76 ea                   jbe    400633 <main+0x7d>
  400649:       48 8d 45 b0             lea    -0x50(%rbp),%rax
  40064d:       48 89 45 e0             mov    %rax,-0x20(%rbp)
  400651:       0f 01 f9                rdtscp