igo*_*gon 6 c x86 assembly gcc perf
我最近一直在使用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;
}
Run Code Online (Sandbox Code Playgroud)
我用gcc 4.6.3编译:
gcc -Wall -O benchmark.c -o benchmark
Run Code Online (Sandbox Code Playgroud)
并且它确实编译为一个非常简单的程序集(用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
Run Code Online (Sandbox Code Playgroud)
三个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
Run Code Online (Sandbox Code Playgroud)
这是一台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
Run Code Online (Sandbox Code Playgroud)
退役的装载和存储如何计算每个针对内存的mov操作?为什么即使没有实际从内存中读取数据,怎么会有这么多的负载呢?
所以我对此有点好奇并做了一些研究.主要是为了看看自上次使用它以来,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
Run Code Online (Sandbox Code Playgroud)
对.甚至更大的数字.那么发生了什么?让我们更好地记录和分析这个:
$ 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
Run Code Online (Sandbox Code Playgroud)
啊哈,所以内核负责大多数负载和存储.我们得到的计数器计算内核和用户域都执行的缓存访问.
发生的事情是程序的物理页面(包括数据段和bss)在启动程序时没有映射甚至分配.当您第一次触摸它们时(或将来如果它们被分页),内核会将它们排除在外.我们可以看到:
$ cc -O -o foo foo.c && perf stat -e faults ./xx
Performance counter stats for './xx':
58,696 faults
Run Code Online (Sandbox Code Playgroud)
我们实际上只是在一次运行期间执行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;
}
Run Code Online (Sandbox Code Playgroud)
我确保在期间获得所有页面错误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
Run Code Online (Sandbox Code Playgroud)
你有它.页面错误发生在调用期间memset
,有些是在动态链接期间发生的memset
,之前主要发生的噪音现在发生,bench
本身没有任何负载和大约3000万个商店.就像我们预期的那样.这里有一个有趣的注意事项是memset
知道如何在这台机器上有效率,并且只有一半的商店与你的测试相比,以填充相同数量的内存."sse2" __memset_sse2
是一个很好的暗示.
我刚刚意识到有一件事可能不太清楚,我不知道该把它放在哪里,所以我会放在这里.性能计数器会对事件进行准确计数,但据我所知,如果您想知道这些事件发生的位置,CPU每次记录X事件时都只能生成一个陷阱.因此,这些工具并不确切知道事件发生的位置(以这种方式运行速度太慢),而是等到陷阱到来并将所有X事件记录到该指令/函数.我想,但我不确定X是否至少为10000.因此,如果该函数bench
只触及堆栈一次并且恰好生成了L1-dcache-load溢出陷阱,那么您将对该读取的读数进行10000次读取堆.此外,据我所知,bench
函数中的TLB未命中(其中你将得到58593左右)也通过L1缓存解析,并将计入其中.所以无论你做什么,你都永远不会得到你期望的数字.
归档时间: |
|
查看次数: |
392 次 |
最近记录: |