测量内存访问时间x86

时间:2018-01-13 19:22:07

标签: performance caching assembly memory x86

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

以下是代码:

Test-Path '\\XXXX\China-team\Release Package\\CustomerDB_file\CustomerDB.txt'

输出:

Test-Path '\\XXXX\China-team\Release Package\CustomerDB_file\CustomerDB.txt'

我预计,第一个值会大得多,因为在案例(1)中clflush会使缓存无效。

有关我的cpu(Intel(R)Core(TM)i7 CPU Q 720 @ 1.60GHz)的信息缓存:

  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 } 

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

(1) tsc: 451248
(2) tsc: 449568

看起来我失踪/误解了什么。你能建议吗?

1 个答案:

答案 0 :(得分:4)

mov %0, %%al非常慢(每64个时钟一个缓存行,or per 32 clocks on Sandybridge specifically (not Haswell or later)),无论您的负载是否最终来自DRAM或L1D,您可能会遇到瓶颈。

只有每个第64个负载都会在高速缓存中丢失,因为您可以通过微小的字节加载循环充分利用空间局部性。如果您确实想要在刷新L1D大小的块之后测试缓存可以重新填充的速度,则应该使用SIMD movdqa循环,或者仅使用跨度为64的字节加载。(您只需要触摸一个字节每个缓存行。)

为避免错误依赖RAX的旧值,您应该使用movzbl %0, %eax。这将使Sandybridge和更高版本(或自K8以来的AMD)使用每个时钟2个负载的全负载吞吐量来保持内存管道更接近满。多个高速缓存未命中可以同时进行:英特尔CPU内核有10个LFB(线路填充缓冲区)用于到/从L1D的线路,或16个超级条目用于从L2到核心的线路。另见Why is Skylake so much better than Broadwell-E for single-threaded memory throughput?。 (多核Xeon芯片的单线程内存带宽比台式机/笔记本电脑差。)

但是你的瓶颈远远不如此!

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

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

这个肯定是意味着你的循环不会出现缓存未命中的问题,并且无论你是否使用clflush,都可能以相同的速度运行。

另请注意,rdtsc计算参考周期,而非核心时钟周期。即,无论CPU运行速度较慢(动力传动)还是速度较快(Turbo),它的1.7GHz CPU始终都会达到1.7GHz。通过预热循环控制此事。

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

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