为什么Visual Studio在对指针进行解引用操作前会先将其递增?

7

我从下面的SIMD代码中查看了Visual Studio 2012的汇编输出:

    float *end = arr + sz;
    float *b = other.arr;
    for (float *a = arr; a < end; a += 4, b += 4)
    {
        __m128 ax = _mm_load_ps(a);
        __m128 bx = _mm_load_ps(b);
        ax = _mm_add_ps(ax, bx);
        _mm_store_ps(a, ax);
    }

循环体是:
$LL11@main:
    movaps  xmm1, XMMWORD PTR [eax+ecx]
    addps   xmm1, XMMWORD PTR [ecx]
    add ecx, 16                 ; 00000010H
    movaps  XMMWORD PTR [ecx-16], xmm1
    cmp ecx, edx
    jb  SHORT $LL11@main

为什么要将ecx增加16,只有在下一行存储到它时才减去16?


(提示:这可能是由于栈帧对齐所致)

还有另一件奇怪的事情:我猜它决定将两个数组地址之间的差异存储在eax中,并在第一次加载时添加它,而不是保持两个指针并将两者都增加16。除了内存访问之外的额外加法基本上是免费的吗? - japreiss
你正在使用哪些优化选项? - soandos
3个回答

7

嗯,这里基本上有两个选项。

 add ecx, 16
 movaps XMMWORD PTR [ecx-16], xmm1 ; stall for ecx?
 cmp ecx, edx
 jb loop

或者
 movaps XMMWORD PTR [ecx], xmm1
 add ecx, 16
 cmp ecx, edx ; stall for ecx?
 jb loop

在选项1中,您可能会在addmovaps之间遇到潜在的停顿。在选项2中,您可能会在addcmp之间遇到潜在的停顿。但是,还存在使用执行单元的问题。addcmp(=sub)使用ALU,而[ecx-16]使用AGU(地址生成单元),我相信。因此,我怀疑在选项1中,由于交错使用了ALU和AGU,可能会略微胜出。

4
ADDPS 的延迟为 3 个时钟周期,加上内存加载时间,因此下一个 ADD 将比 ADDPS 快得多,并且在需要 xmm1 寄存器中 ADDPS 结果的下一个 MOVAPS 开始之前完成。

1

我确实觉得这有点奇怪。

许多编译器会避免在指令修改寄存器后读取它,因为这样的代码在某些处理器上运行速度较慢。例如:

; Code that runs fast:
add ecx, 16
mov esi, edi
cmp ecx, edx

; Code doing the same that may run slower:
mov esi, edi
add ecx, 16
cmp ecx, edx

由于这个原因,编译器经常更改汇编指令的顺序。然而在您的情况下,这绝对不是原因。
也许编译器的优化代码没有完全正确编写,因此会进行这种“优化”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接