我们是两名HPC学生参与了着名的SchönauerTriadBenchmark,其中的C代码及其简短说明如下:
var layoutS =
{
title:'Plot',
xaxis1:{
zeroline:false,
zerolinewidth:1,
showline:true,
showticklabels:true,
ticks:'outside',
},
yaxis1:{
zeroline:true,
zerolinewidth:1,
showline:true,
showticklabels:true,
ticks:'outside',
},
width:graphWidth,
height:graphHeight,
annotations:[
{
xref:'paper',
yref:'paper',
x:0,
xanchor:'center',
y:1.1,
yanchor:'bottom ',
text:'x[n]',
showarrow:false
},
{
xref:'paper',
yref:'paper',
x:1.1,
xanchor:'left',
y:0,
yanchor:'top',
text:'n',
showarrow:false
}
]
}
代码只是循环遍历N,对于每个 N ,它执行 NR 浮点运算,其中 NR 是一个代表N的常量每次最外迭代时要进行的常数操作次数,以便即使对于太短的N值也能进行准确的时间测量。要分析的内核显然是 simulation 子例程。
我们得到了一些奇怪的结果:
我们开始在 E4 E9220服务器2U 上对内核进行基准测试,该服务器由8个节点组成,每个节点都配有双插槽 Intel Xeon E5-2697 V2(Ivy Bridge)@ 2,7 GHz,12核。该代码已使用 gcc(GCC)4.8.2 进行编译,并已在 Linux CentOS版本6 上运行。下面列出了单个图像中的结果图:
N versus MFlops plots: -Ofast (above) and -Ofast along -march=native (below)
直接看到L2和L3下坡非常明显,并且通过做一些简单的计算并考虑到多道程序设计问题以及L2-L3统一且L3在所有12个中共享的事实在数值上是可以的。核心。在第一个图中,L1是不可见的,而在第二个图中,它是可见的,它以N值开始,因此根据每个核心的L1D大小,得到的L1D饱和度值正好是32 KB。第一个问题是:为什么我们不在没有 -march = native 架构专业化标志的情况下看到L1下坡?
经过一些棘手的(明显错误的)自我解释后,我们决定在 Lenovo Z500 上进行基准测试,配备单个插座 Intel Core i7-3632QM(Ivy Bridge)@ 2.2 GHz 。这次我们使用了gcc (Ubuntu 6.3.0-12ubuntu2)6.3.0 20170406 (来自 gcc --version ),结果图如下:< / p>
N versus MFlops plots: -Ofast (above) and -Ofast along -march=native (below)
第二个问题有些自发:为什么我们看到L1D下坡而没有 -march = native - 这次?
答案 0 :(得分:0)
内部“TRIAD”循环的汇编片段(A[i] = B[i] + C[i]*D[i]
:每i
次迭代2次double_precision触发器,3次读取double,1次写入double。
来自perf annotate
的确切百分比并不是非常有用,因为您将具有不同性能的所有区域分析为单次运行。并且long perf报告根本没有用,通常只需要#
之后的5-10个第一行。您可以尝试将测试限制在4 * N * sizeof(double)<4的感兴趣区域。 sizeof(L1d_cache)并重新收集perf注释并获得perf stat ./program
和perf stat -d ./program
的结果(并且还了解特定于英特尔的性能包装器ocperf.py
- https://github.com/andikleen/pmu-tools以及其他工具)
从gcc-6.3.0 -Ofast
- 使用128位(2个双精度)XMM registers和SSE2 movupd/movups(SSE2是x86_64 cpu的默认FPU),每次i
次迭代2次汇编程序循环(movupd从内存加载2个双打)
: A[i] = B[i] + C[i]*D[i];
0.03 : d70: movupd (%r11,%rax,1),%xmm1 # load C[i:i+1] into xmm1
14.87 : d76: add $0x1,%ecx # advance 'i/2' loop counter by 1
0.10 : d79: movupd (%r10,%rax,1),%xmm0 # load D[i:i+1] into xmm0
14.59 : d7f: mulpd %xmm1,%xmm0 # multiply them into xmm0
2.78 : d83: addpd (%r14,%rax,1),%xmm0 # load B[i:i+1] and add to xmm0
17.69 : d89: movups %xmm0,(%rsi,%rax,1) # store into A[i:i+1]
2.71 : d8d: add $0x10,%rax # advance array pointer by 2 doubles (0x10=16=2*8)
1.68 : d91: cmp %edi,%ecx # check for end of loop (edi is N/2)
0.00 : d93: jb d70 <main+0x4c0> # if not, jump to 0xd70
来自gcc-6.3.0 -Ofast -march=native
:vmovupd不仅仅是向量(SSE2 某些 pd也是向量),它们是AVX instructions,可以使用2倍宽的寄存器YMM(256位,每个寄存器4个双打)。循环时间较长,但每次循环迭代处理4 i
次迭代
0.02 : db6: vmovupd (%r10,%rdx,1),%xmm0 # load C[i:i+1] into xmm0 (low part of ymm0)
8.42 : dbc: vinsertf128 $0x1,0x10(%r10,%rdx,1),%ymm0,%ymm1 # load C[i+2:i+3] into high part of ymm1 and copy xmm0 into lower part; ymm1 is C[i:i+3]
7.37 : dc4: add $0x1,%esi # loop counter ++
0.06 : dc7: vmovupd (%r9,%rdx,1),%xmm0 # load D[i:i+1] -> xmm0
15.05 : dcd: vinsertf128 $0x1,0x10(%r9,%rdx,1),%ymm0,%ymm0 # load D[i+2:i+3] and get D[i:i+3] in ymm0
0.85 : dd5: vmulpd %ymm0,%ymm1,%ymm0 # mul C[i:i+3] and D[i:i+3] into ymm0
1.65 : dd9: vaddpd (%r11,%rdx,1),%ymm0,%ymm0 # soad 4 doubles of B[i:i+3] and add to ymm0
21.18 : ddf: vmovups %xmm0,(%r8,%rdx,1) # store low 2 doubles to A[i:i+1]
1.24 : de5: vextractf128 $0x1,%ymm0,0x10(%r8,%rdx,1) # store high 2 doubles to A[i+2:i+3]
2.04 : ded: add $0x20,%rdx # advance array pointer by 4 doubles
0.02 : df1: cmp -0x460(%rbp),%esi # loop cmp
0.00 : df7: jb db6 <main+0x506> # loop jump to 0xdb6
启用AVX的代码(使用-march=native
)更好,因为它使用更好的展开,但它使用2个双倍的窄负载。通过更多真实测试,阵列将更好地对齐,编译器可以选择最宽256-bit vmovupd到ymm,而无需插入/提取指令。
你现在的代码可能很慢 无法完全加载(饱和)接口到L1数据缓存在大多数情况下使用短数组。另一种可能是阵列之间的对齐不良。
在https://i.stack.imgur.com/2ovxm.png - 6“GFLOPS”的下图中,您的高带宽短暂出现,这很奇怪。进行计算以将其转换为GByte / s并找到Ivy Bridge的L1d带宽和负载发布率的限制......类似于https://software.intel.com/en-us/forums/software-tuning-performance-optimization-platform-monitoring/topic/532346“ Haswell核心每个周期只能发出两个负载,所以它们必须是256位AVX负载,才有可能达到64字节/周期的速率。“(TRIAD的专家和STREAM的作者,John D. McCalpin,博士”Bandwidth博士“ ,搜索他的帖子)和http://www.overclock.net/t/1541624/how-much-bandwidth-is-in-cpu-cache-and-how-is-it-calculated“ L1带宽取决于每个滴答的指令和指令的步幅(AVX = 256位,SSE = 128位等).IIRC,Sandy Bridge每个tick有1条指令“