TL:DR: LLVM-MCA 分析了这些注释之间的整个代码块,就好像它是body一个循环,并向您显示所有这些指令 100 次迭代的循环计数。
但除了实际(微小)循环之外,大多数指令都是循环设置以及循环后的 SIMD 水平求和,实际上只运行一次。 (这就是为什么周期数是数千,而不是 400 = 100 倍的 4 周期延迟vaddpd
在 Skylake 上0.0
版本带有double
累加器。)
如果您取消选中 Godbolt 编译器资源管理器上的“//”框,或者修改 asm 语句以添加 nop ,例如"nop # LLVM-MCA-END"
,您将能够在 asm 窗口中找到这些行,并查看 LLVM-MCA 正在查看的内容,因为它是“循环”。
LLVM MCA 模拟指定的汇编指令序列并计算估计执行所需的周期数每次迭代在指定的目标架构上。 LLVM MCA 进行了许多简化,例如(我突然想到):(1)它假设所有条件分支都失败,(2)它假设所有内存访问都是写回内存类型并且全部命中L1 缓存,(3) 假设前端工作最佳,(4)call
被调用的过程中没有遵循指令,它们就会失败。还有其他假设,我目前不记得了。
本质上,LLVM MCA(如英特尔 IACA)仅适用于后端计算绑定的简单循环。在 IACA 中,虽然支持大多数指令,但仍有少数指令没有详细建模。例如,假设预取指令仅消耗微架构资源,但延迟基本上为零,并且对存储器层次结构的状态没有影响。然而,在我看来,马华完全忽视了这样的指示。无论如何,这与你的问题不是特别相关。
现在回到你的代码。在您提供的编译器资源管理器链接中,您没有将任何选项传递给 LLVM MCA。因此,默认的目标架构生效,即该工具运行的任何架构。这恰好是SKX。您提到的总周期数是针对 SKX 的,但不清楚您是否在 SKX 上运行代码。您应该使用-mcpu
指定架构的选项。这独立于-march
你转到了海湾合作委员会。另请注意,将核心周期与毫秒进行比较是没有意义的。您可以使用RDTSC
用于测量以核心周期为单位的执行时间的指令。
请注意编译器如何内联对std::accumulate
。显然,这段代码从装配线 405 开始,也是该指令的最后一条指令。std::accumulate
位于第444行,共38条指令。 LLVM MCA 估计与实际性能不符的原因现在已经很清楚了。该工具假设所有这些指令都在循环中执行大量迭代。显然事实并非如此。 420-424之间只有一个循环:
.L75:
vaddpd ymm0, ymm0, YMMWORD PTR [rax]
add rax, 32
cmp rax, rcx
jne .L75
仅应将此代码输入到 MCA。在源代码级别,确实没有办法告诉 MCA 只分析这段代码。你必须手动内联std::accumulate
并放置LLVM-MCA-BEGIN
and LLVM-MCA-END
在其内部某处进行标记。
经过时0ULL
代替0.0
to std::accumulate
,LLVM MCA 的输入将从汇编指令 402 开始,到 441 结束。请注意,MCA 不支持任何指令(例如vcvtsi2sdq
)将在分析中完全省略。代码中实际处于循环中的部分是:
.L78:
vxorpd xmm0, xmm0, xmm0
vcvtsi2sdq xmm0, xmm0, rax
test rax, rax
jns .L75
mov rcx, rax
and eax, 1
vxorpd xmm0, xmm0, xmm0
shr rcx
or rcx, rax
vcvtsi2sdq xmm0, xmm0, rcx
vaddsd xmm0, xmm0, xmm0
.L75:
vaddsd xmm0, xmm0, QWORD PTR [rdx]
vcomisd xmm0, xmm1
vcvttsd2si rax, xmm0
jb .L77
vsubsd xmm0, xmm0, xmm1
vcvttsd2si rax, xmm0
xor rax, rdi
.L77:
add rdx, 8
cmp rsi, rdx
jne .L78
注意这里有一个条件跳转,jns
,在目标地址位于块中某处的代码中。 MCA 只会假设跳跃会失败。如果实际运行代码时情况并非如此,MCA 将不必要地增加 7 条指令的开销。还有一次跳跃,jb
,但我认为这对于大向量来说并不重要,并且大多数时候都会失败。最后一跳,jne
,也是最后一条指令,因此 MCA 会假设下一条指令又是最上面的指令。对于足够多的迭代次数,这个假设是完全正确的。
总的来说,很明显第一个代码比第二个代码小得多,因此它可能要快得多。您的测量确实证实了这一点。您实际上也不需要使用微架构分析工具来理解原因。第二个代码只是做了更多的计算。所以你可以很快得出结论:通过0.0
在所有架构上的性能和代码大小方面都更好。