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

标签 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()所有 4 个浮点数都被 %xmm0 传递.不会发生对内存的写入。
  • 打电话时printv()b() 2 个浮点数正在通过 %xmm0和另外两个 float %xmm1 .为此,将 4 个浮点数加载 (.LC0) 到 %xmm2从那里到内存。之后,从内存中的同一个地方读取2个浮点数到%xmm0和其他 2 个浮点数加载 (.LC1) 到 %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在相邻的长词中。

    最佳答案

    好问题,我不得不挖一点,因为我从来没有用过 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
    

    现在您在单独的 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
    

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

    关于gcc - GCC 内置向量化类型和 C 数组之间有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16618998/

    相关文章:

    c++ - 将 -M 标志添加到 g++ 会导致文件格式无法识别错误

    c++ - mac build 有 undefined symbol ,而 linux build 没有相同的代码

    c - 使用单精度 float 理解/编写用于汇编的 C 代码

    c++ - SSE:如何将 _m128i._i32[4] 减少到 _m128i._i8

    SSE2 : How to reduce a _m128 to a word

    c++ - GCC 元组 "invalid use of incomplete type"

    c++ - glibc 版本早于 gcc 版本和 -Wl,-rpath 不起作用

    c - DWARF - 如何在给定的二进制文件中找到函数的序言结束/结尾开始地址?

    assembly - objdump 输出中的 "-0x1(%edx,%ecx,1)"是什么意思?

    gcc - 如何在GCC中的32字节边界处对齐堆栈?