6

我有三个函数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);
}

当我编译这段代码时,我观察到以下行为:

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

为什么a()b()不同c()

这是 a() 的汇编输出:

        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相邻的长字。

4

1 回答 1

1

好问题,我不得不挖掘一点,因为我自己从未使用过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.0并被4.0移动(vmovq 移动四字,即 64 位)到 XMM1

    vmovq   .LC1(%rip), %xmm1

最后是矢量化的低 64 位(位 0-63),即浮点数1.0,并2.0从内存移动到 XMM0

    vmovq   (%rsp), %xmm0

现在,您在单独的 XMM(0-7) 寄存器中拥有了 128 位向量的上半部分和下半部分。

现在以防万一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

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

于 2013-06-05T11:27:23.517 回答