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大小的块后缓存重新填充的速度,那么应该使用SIMDmovdqa
循环,或者只使用跨距为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大小的块后缓存重新填充的速度,那么应该使用SIMDmovdqa
循环,或者只使用跨距为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