我试着理解SSE指令的矢量化是如何工作的。
这里是一个实现矢量化的代码片段:
#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代表&#34; x&#34;数组?
%rsi在C代码段中代表什么?
最终结果(例如[0] = a [0] + b [0])是否存储在%rdi中?
感谢您的帮助
答案 0 :(得分:5)
您需要知道的第一件事是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编译器的低效率。现代英特尔处理器可以将cmp
和jne
指令融合到一条指令中,它们还可以将add
和jne
融合到一条指令中,但它们不能将add
,cmp
和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
指令,add
和jnz
将融合到一个微观-operation。
答案 1 :(得分:3)
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是&amp; x,rsi是&amp; y。每次遍历循环都会增加两个双精度数,因此rax的增量为2 * sizeof(double)或16个字节。