我正在使用linux perf工具来分析one of CRONO benchmarks,我对L1 DCache Misses特别感兴趣,所以我运行这样的程序:
perf record -e L1-dcache-read-misses -o perf/apsp.cycles apps/apsp/apsp 4 16384 16
运行正常,但会产生警告:
WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,
check /proc/sys/kernel/kptr_restrict.
Samples in kernel functions may not be resolved if a suitable vmlinux
file is not found in the buildid cache or in the vmlinux path.
Samples in kernel modules won't be resolved at all.
If some relocation was applied (e.g. kexec) symbols may be misresolved
even with a suitable vmlinux or kallsyms file.
Cannot read kernel map
Couldn't record kernel reference relocation symbol
Symbol resolution may be skewed if relocation was used (e.g. kexec).
Check /proc/kallsyms permission or run as root.
Threads Returned!
Threads Joined!
Time: 2.932636 seconds
[ perf record: Woken up 5 times to write data ]
[ perf record: Captured and wrote 1.709 MB perf/apsp.cycles (44765 samples) ]
然后我按如下方式注释输出文件:
perf annotate --stdio -i perf/apsp.cycles --dsos=apsp
但是在其中一个代码部分中,我看到了一些奇怪的结果:
Percent | Source code & Disassembly of apsp for L1-dcache-read-misses
---------------------------------------------------------------------------
: {
: if((D[W_index[v][i]] > (D[v] + W[v][i])))
19.36 : 401140: movslq (%r10,%rcx,4),%rsi
14.50 : 401144: lea (%rax,%rsi,4),%rdi
1.22 : 401148: mov (%r9,%rcx,4),%esi
5.82 : 40114c: add (%rax,%r8,4),%esi
20.02 : 401150: cmp %esi,(%rdi)
0.00 : 401152: jle 401156 <do_work(void*)+0x226>
: D[W_index[v][i]] = D[v] + W[v][i];
9.72 : 401154: mov %esi,(%rdi)
19.93 : 401156: add $0x1,%rcx
:
现在在那些结果中,为什么一些算术指令有L1读取未命中?另外,为什么第二个语句的指令会导致如此多的缓存未命中,即使它们应该通过前面的if语句进入缓存呢? 我在这里做错了吗?我在具有root访问权限的另一台计算机上尝试了相同的操作,它给了我类似的结果,所以我认为上面提到的警告不会导致这种情况。但到底是怎么回事?
答案 0 :(得分:0)
所以我们有这个代码:
for(v=0;v<N;v++)
{
for(int i = 0; i < DEG; i++)
{
if((/* (V2) 1000000000 * */ D[W_index[v][i]] > (D[v] + W[v][i])))
D[W_index[v][i]] = D[v] + W[v][i];
Q[v]=0; //Current vertex checked
}
}
请注意,我在代码中添加了(V2)作为注释。我们在下面回到这段代码。
第一次近似
请注意,W_index
已初始化为W_index[i][j] = i + j
(A) 。
让我们关注一个内部迭代,首先让我们假设DEG
很大。此外,我们假设缓存足够大,可以保存所有数据至少两次迭代。
D[W_index[v][i]]
查找W_index[v]
被加载到寄存器中。对于W_index[v][i]
,我们假设一个缓存未命中(64字节缓存行,每个int 4个字节,我们调用DIM = 16的程序)。 D
中的查找始终位于v
,因此数组中大部分必需部分已经在缓存中。假设DEG
很大,这个查找是免费的。
D[v] + W[v][i]
查找D[v]
是免费的,因为它取决于v
。第二个查找与上面相同,第二个维度的一个缓存未命中。
整个内心的陈述没有影响力。
Q[v]=0;
由于这是v
,因此可以忽略。
当我们总结时,我们得到两个缓存未命中。
第二次近似
现在,我们回到假设DEG
很大。事实上这是错误的,因为DEG = 16
。因此,我们还需要考虑一些缓存未命中。
D[W_index[v][i]]
查找W_index[v]
占用高速缓存未命中的1/8(它的大小为8字节,高速缓存行为64字节,因此每次重复迭代时都会丢失一次)。
D[W_index[v][i]]
也是如此,只有D
包含整数。平均而言,除了一个整数之外的所有整数都在缓存中,因此这需要1/16的缓存未命中。
D[v] + W[v][i]
D[v]
已经在缓存中(这是W_index[v][0]
)。但是,对于W[v]
,我们得到另外1/8的缓存未命中,原因与上述相同。
Q[v]=0;
这是另一个1/16的缓存未命中。
令人惊讶的是,如果我们现在使用if
- 子句永远不会评估为true
的代码(V2),每次迭代我得到2.395缓存未命中(注意你真的需要配置你的CPU好吧,即没有超线程,没有turboboost,性能调控器,如果可能的话)。上面的计算将导致2.375。所以我们非常好。
第三次近似
现在有一个不幸的if
条款。此比较与true
的评估频率如何?我们不能说,一开始它会经常发生,最终它永远不会评估为true
。
因此,让我们专注于完整循环的第一次执行。在这种情况下,D[v]
是无穷大,W[v][i]
是1到101之间的数字。因此循环在每次迭代中的计算结果为true
。
然后它变得很难 - 我们在这次迭代中得到2.9缓存未命中。他们来自哪里 - 所有数据都应该已经在缓存中。
但是:这是“编译器之谜”。你永远不知道它们最终会产生什么。我与GCC和Clang编译并采取相同的措施。我激活了-funroll-loops
,突然间我得到了2.5次缓存未命中。当然,这可能与您的系统有所不同。当我检查装配时,我发现它确实完全相同,只是循环已经展开了四次。
那么这告诉我们什么?除了检查之外,你永远不知道你的编译器做了什么。即使这样,你也无法确定。
我猜硬件预取或执行顺序可能会对此产生影响。但这是一个谜。
关于性能和你的问题
我认为您所做的测量有两个问题:
我的经验是,当您想要为代码的特定部分获得良好的衡量标准时,您确实需要手动检查它。有时候 - 并非总是如此 - 它可以解释相当不错的事情。