【问题标题】:Understanding of vectorization with SSE instructions用 SSE 指令理解向量化
【发布时间】:2014-12-09 19:58:00
【问题描述】:

我试图了解使用 SSE 指令进行矢量化的工作原理。

这里是实现矢量化的代码 sn-p:

#include <stdlib.h>
#include <stdio.h>

#define SIZE 10000

void test1(double * restrict a, double * restrict b)
{
  int i;

  double *x = __builtin_assume_aligned(a, 16);
  double *y = __builtin_assume_aligned(b, 16);

  for (i = 0; i < SIZE; i++)
  {
    x[i] += y[i];
  }
}

还有我的编译命令:

gcc -std=c99 -c example1.c -O3 -S -o example1.s

这里是汇编代码的输出:

 .file "example1.c"
  .text
  .p2align 4,,15
  .globl  test1
  .type test1, @function
test1:
.LFB7:
  .cfi_startproc
  xorl  %eax, %eax
  .p2align 4,,10
  .p2align 3
.L3:
  movapd  (%rdi,%rax), %xmm0
  addpd (%rsi,%rax), %xmm0
  movapd  %xmm0, (%rdi,%rax)
  addq  $16, %rax
  cmpq  $80000, %rax
  jne .L3
  rep ret
  .cfi_endproc
.LFE7:
  .size test1, .-test1
  .ident  "GCC: (Debian 4.8.2-16) 4.8.2"
  .section  .note.GNU-stack,"",@progbits

我多年前练习过Assembler,我想知道上面代表什么 寄存器 %rdi、%rax 和 %rsi。

我知道 %xmm0 是 SIMD 寄存器,我们可以在其中存储 2 个双精度数据(16 个字节)。

但我不明白如何同时添加:

我认为一切都发生在这里:

      movapd  (%rdi,%rax), %xmm0
      addpd (%rsi,%rax), %xmm0
      movapd  %xmm0, (%rdi,%rax)
      addq  $16, %rax
      cmpq  $80000, %rax
      jne .L3
      rep ret

%rax 代表“x”数组吗?

%rsi 在 C 代码 sn-p 中代表什么?

最后的结果(比如a[0]=a[0]+b[0]是否存入%rdi?

感谢您的帮助

【问题讨论】:

  • 您正在生成 64 位代码,而 rXX 寄存器只是普通 x86 寄存器的 64 位版本(例如 eax = 32 位,rax = 64 位)。
  • 使用-fverbose-asm 让 gcc 在每条指令上放置 cmets,为每个操作数命名。 (通常它们只是编号的 tmp 名称,但有时您会得到一个有意义的 C 变量名称。特别是对于数组索引。)

标签: c assembly x86 vectorization auto-vectorization


【解决方案1】:
  movapd  (%rdi,%rax), %xmm0       ; temp = x[i]
  addpd (%rsi,%rax), %xmm0         ; temp += y[i]
  movapd  %xmm0, (%rdi,%rax)       ; x[i] = temp
  addq  $16, %rax                  ; i += 2
  cmpq  $80000, %rax               ; if (i < SIZE)
  jne .L3                          ; then loop

rax 寄存器表示i 变量,但存储为字节索引,rdi 为 &x,rsi 为 &y。每次通过循环都会添加 两个 双精度数,因此 rax 会增加 2 * sizeof(double) 或 16 个字节。

【讨论】:

    【解决方案2】:

    您需要了解的第一件事是 Unix 系统上 64 位代码的调用约定。请参阅Wikipedia's x86-64_calling_conventions,有关更多详细信息,请阅读 Agner Fog 的calling conventions manual

    整数参数按以下顺序传递:rdi、rsi、rdx、rcx、r8、r9。因此,您可以通过寄存器传递六个整数值(但在 Windows 上只有四个)。这意味着在您的情况下:

    rdi = &x[0],
    rsi = &y[0].
    

    寄存器rax 从零开始,每次迭代递增2*sizeof(double)=16 字节。然后在每次迭代时将其与sizeof(double)*10000=80000 进行比较,以测试循环是否完成。

    这里使用cmp其实是GCC编译器效率低下。现代英特尔处理器可以将cmpjne 指令融合为一条指令,也可以将addjne 融合为一条指令,但它们不能融合addcmp , 和 jne 到一条指令中。不过可以remove the cmp instruction

    GCC应该做的事情已经设定

    rdi = &x[0] + 80000;
    rsi = &y[0] + 80000;
    rax = -80000
    

    然后循环可以这样完成

    movapd  (%rdi,%rax), %xmm0       ; temp = x[i]
    addpd (%rsi,%rax), %xmm0         ; temp += y[i]
    movapd  %xmm0, (%rdi,%rax)       ; x[i] = temp
    addq  $16, %rax                  ; i += 2
    jnz .L3                          ; then loop
    

    现在循环从-80000计数到0,不需要cmp指令,addjnz将融合为一个微操作。

    【讨论】:

      猜你喜欢
      • 2011-11-08
      • 2020-03-12
      • 2010-10-09
      • 2019-04-03
      • 1970-01-01
      • 2016-01-23
      • 1970-01-01
      • 2012-02-27
      • 1970-01-01
      相关资源
      最近更新 更多