我有三个函数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.0
和4.0
。
答案 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.0
和4.0
被移动(vmovq移动四字,即64位)到XMM1
vmovq .LC1(%rip), %xmm1
最后向量化的低64位(位0-63),即浮点1.0
和2.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
在所有三种情况下,矢量化的处理方式不同。希望这会有所帮助。