我尝试测量缓存/非缓存内存访问时间,结果让我感到困惑。
以下是代码:
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
看起来我失踪/误解了什么。你能建议吗?
答案 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。