我有三个功能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.0
和 4.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.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
在所有三种情况下,矢量化的处理方式不同。希望这可以帮助。
关于gcc - GCC 内置向量化类型和 C 数组之间有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16618998/