Dad*_*ada 5 performance x86 assembly intel perf
TL;DR我有一个循环需要 1 个周期才能在 Skylake 上执行(它执行 3 次加法 + 1 次增加/跳跃)。
当我展开它超过 2 次(无论多少)时,我的程序运行速度会慢 25%。这可能与对齐有关,但我不清楚是什么。
编辑:这个问题曾经询问为什么 uops 是由 DSB 而不是 MITE 提供的。这现在已经转移到这个问题上。
我试图对一个循环进行基准测试,该循环在我的 Skylake 上添加了 3 个。这个循环应该在一个周期内执行,因为 3 add + 1 increment 与条件跳转融合,一旦融合可以在一个周期内执行。正如预期的那样。
然而,在某些时候,我的 C 编译器试图展开该循环,从而产生更差的性能。我现在试图理解为什么展开循环的性能比非展开循环更差,因为我希望两者具有相同的性能,或者展开循环的速度可能会慢 15%以下。
这是我的 C 代码:
int main() {
int a, b, c, d;
#pragma unroll(2)
for (unsigned long i = 0; i < 2000000000; i++) {
asm volatile("" : "+r" (a), "+r" (b), "+r" (c), "+r" (d));
a = a + d;
b = b + d;
c = c + d;
}
// Prevent data from being optimized out
asm volatile("" : "+r" (a), "+r" (b), "+r" (c));
}
Run Code Online (Sandbox Code Playgroud)
使用 Clang 7.0.0 -O3 进行编译会生成以下(清理过的)程序集(v1从现在开始调用):
movl $2000000000, %esi
.p2align 4, 0x90
.LBB0_1:
addl %edi, %edx
addl %edi, %ecx
addl %edi, %eax
addl %edi, %edx
addl %edi, %ecx
addl %edi, %eax
addq $-2, %rsi
jne .LBB0_1
Run Code Online (Sandbox Code Playgroud)
基准测试perf stat -e cycles表明每次迭代大约需要 2 个周期。
但是,用“新的 64 位寄存器”(r8 到 r15)替换任何寄存器会导致循环执行 3 个周期而不是 2 个周期(我们称之为代码v2):
movl $2000000000, %esi
.p2align 4, 0x90
.LBB0_1:
addl %edi, %r14d
addl %edi, %ecx
addl %edi, %eax
addl %edi, %r14d
addl %edi, %ecx
addl %edi, %eax
addq $-2, %rsi
jne .LBB0_1
Run Code Online (Sandbox Code Playgroud)
这不是一个随机的例子:如果我向我的程序添加一些东西并且不走运,Clang 实际上会产生这个循环(我的初始版本是相同的 C 代码,附加随机初始化变量、预热阶段和 rdtscp 来计时循环,以及循环中使用r14d的Clang )。该循环以大约 3 个周期/迭代执行。
进一步的测试表明,将循环展开任意次数大于 2 会使程序执行 25 亿次循环(而非展开循环时为 20 亿次)。
一个循环中的 uops 数是3*n+1(其中n是展开因子,1代表的是 fused add/jne),这意味着循环展开 3 次有 10 个 uops;4 倍 13 uop 等等。这些是相当少量的 uop,应该适合 DSB(uop 缓存)。我在 Skylake 上更新了微代码,并修复了 SKL150,因此我的 LSD 循环缓冲区被禁用。
此外,展开 3、4、10 或 50 次根本不会改变性能:我的代码始终运行 25 亿个周期(而非展开一个运行 20 亿个周期)。这有点令人惊讶,因为 3 个加法应该总是在 1 个周期内执行,因此,如果由于某种原因在循环结束时丢失了一个额外的周期,那么它的开销应该在展开增加时摊销,并且渐近(在展开中) factor) 性能应该接近 20 亿个周期。
双方llvm-mca并iaca预测,展开ň次将会使循环中执行ñ周期(这将使整个程序2个十亿周期执行)。
总而言之,问题是:为什么我展开超过 2 次后,我的循环就会慢 25%?
| 归档时间: |
|
| 查看次数: |
216 次 |
| 最近记录: |