GCC内置矢量化类型和C数组之间有什么区别?

时间:2013-05-17 22:54:31

标签: gcc assembly sse vectorization

我有三个函数a()b()c()应该做同样的事情:

typedef float Builtin __attribute__ ((vector_size (16)));

typedef struct {
        float values[4];
} Struct;

typedef union {
        Builtin b;
        Struct s;
} Union;

extern void printv(Builtin);
extern void printv(Union);
extern void printv(Struct);

int a() {
        Builtin m = { 1.0, 2.0, 3.0, 4.0 };
        printv(m);
}

int b() {
        Union m = { 1.0, 2.0, 3.0, 4.0 };
        printv(m);
}

int c() {
        Struct m = { 1.0, 2.0, 3.0, 4.0 };
        printv(m);
}

编译此代码时,我会发现以下行为:

  • printv()中调用a()时,%xmm0正在传递所有4个浮点数。不会写入内存。
  • printv()中调用b()时,%xmm0正在传递2个浮点数,而%xmm1正在传递另外两个浮点数。为了实现这一点,将4个浮点数(.LC0)加载到%xmm2并从那里加载到内存中。之后,从内存中的同一位置读取2个浮点数到%xmm0,其他2个浮点数被加载(.LC1)到%xmm1
  • 我对c()实际做的事情感到有些失落。

为什么a()b()c()有所不同?

这是()的汇编输出:

        vmovaps .LC0(%rip), %xmm0
        call    _Z6printvU8__vectorf

b()的程序集输出:

        vmovaps .LC0(%rip), %xmm2
        vmovaps %xmm2, (%rsp)
        vmovq   .LC1(%rip), %xmm1
        vmovq   (%rsp), %xmm0
        call    _Z6printv5Union

c()的汇编输出:

         andq    $-32, %rsp
         subq    $32, %rsp
         vmovaps .LC0(%rip), %xmm0
         vmovaps %xmm0, (%rsp)
         vmovq   .LC2(%rip), %xmm0
         vmovq   8(%rsp), %xmm1
         call    _Z6printv6Struct

数据:

        .section        .rodata.cst16,"aM",@progbits,16
        .align 16
.LC0:
        .long   1065353216
        .long   1073741824
        .long   1077936128
        .long   1082130432
        .section        .rodata.cst8,"aM",@progbits,8
        .align 8
.LC1:
        .quad   4647714816524288000
        .align 8
.LC2:
        .quad   4611686019492741120

四元4647714816524288000似乎只不过是相邻长词中的浮点3.04.0

1 个答案:

答案 0 :(得分:0)

不错的问题,我不得不挖掘一点,因为我自己从未使用SSE(在本例中为SSE2)。基本上,向量指令用于对一个寄存器中存储的多个值进行操作,即XMM(0-7)寄存器。在C中,数据类型float使用IEEE 754,因此其长度为32位。使用四个浮点数将产生一个长度为128位的向量,这正好是XMM(0-7)寄存器的长度。现在SSE提供的寄存器如下所示:

SSE (avx-128):                         |----------------|name: XMM0; size: 128bit
SSE (avx-256):        |----------------|----------------|name: YMM0; size: 256bit

在您的第一个案例a()中,您使用SIMD矢量化

typedef float Builtin __attribute__ ((vector_size (16)));

允许您将整个矢量一次性移入XMM0寄存器。现在在你的第二个案例中b()你使用了一个联盟。但是因为您没有将.LC0加载到与Union m.b = { 1.0, 2.0, 3.0, 4.0 };的联合中,所以数据不会被识别为矢量化。这会导致以下行为:

来自.LC0的数据通过以下方式加载到XMM2中:

 vmovaps .LC0(%rip), %xmm2

但是因为您的数据可以解释为结构 作为矢量化,所以数据必须分成两个64位块,仍然必须在XMM(0-7)寄存器中,因为它可以被视为矢量化,但它必须最大64位长才可以传输到寄存器(只有64位宽,如果是128位则会溢出)转移到它;数据丢失)因为数据也可以被视为一个结构。这是在下面完成的。

XMM2中的矢量化用

加载到内存中
    vmovaps %xmm2, (%rsp)

现在向量化的高64位(位64-127),即浮点3.04.0被移动(vmovq移动四字,即64位)到XMM1

    vmovq   .LC1(%rip), %xmm1

最后向量化的低64位(位0-63),即浮点1.02.0从内存移到XMM0

    vmovq   (%rsp), %xmm0

现在,您将128位向量的上部和下部放在单独的XMM(0-7)寄存器中。

现在以及c()我不太确定,但现在就这样了。首先将%rsp与32位地址对齐,然后减去32字节以将数据存储在堆栈中(这将再次与32位地址对齐)这是通过

完成的。
     andq    $-32, %rsp
     subq    $32, %rsp

现在这次将矢量化加载到XMM0中,然后用

放在堆栈上
     vmovaps .LC0(%rip), %xmm0
     vmovaps %xmm0, (%rsp)

最后矢量化的高64位存储在XMM0中,低64位存储在XMM1寄存器中

     vmovq   .LC2(%rip), %xmm0
     vmovq   8(%rsp), %xmm1

在所有三种情况下,矢量化的处理方式不同。希望这会有所帮助。