如何将SIMD int向量转换为在GCC中浮动?

时间:2012-09-11 17:48:07

标签: c gcc vectorization simd

我正在为项目使用GCC SIMD向量扩展,一切都运行得非常好但是强制转换,它们只是重置向量的所有组件。

manual州:

  

可以从一种矢量类型转换为另一种矢量类型,只要它们具有相同的大小(事实上,您也可以将矢量转换为相同大小的其他数据类型)。

这是一个简单的例子:

#include <stdio.h>

typedef int int4 __attribute__ (( vector_size( sizeof( int ) * 4 ) ));
typedef float float4 __attribute__ (( vector_size( sizeof( float ) * 4 ) ));

int main()
{
    int4 i = { 1 , 2 , 3 , 4 };
    float4 f = { 0.1 , 0.2 , 0.3 , 0.4 };

    printf( "%i %i %i %i\n" , i[0] , i[1] , i[2] , i[3] );
    printf( "%f %f %f %f\n" , f[0] , f[1] , f[2] , f[3] );

    f = ( float4 )i;

    printf( "%f %f %f %f\n" , f[0] , f[1] , f[2] , f[3] );
}

使用gcc cast.c -O3 -o cast进行编译并在我的机器上运行,我得到:

1 2 3 4
0.100000 0.200000 0.300000 0.400000
0.000000 0.000000 0.000000 0.000000 <-- no no no

我不是那个汇编大师,但我只看到一些字节移动:

[...]
400454:       f2 0f 10 1d 1c 02 00    movsd  0x21c(%rip),%xmm3
40045b:       00 
40045c:       bf 49 06 40 00          mov    $0x400649,%edi
400461:       f2 0f 10 15 17 02 00    movsd  0x217(%rip),%xmm2
400468:       00 
400469:       b8 04 00 00 00          mov    $0x4,%eax
40046e:       f2 0f 10 0d 12 02 00    movsd  0x212(%rip),%xmm1
400475:       00 
400476:       f2 0f 10 05 12 02 00    movsd  0x212(%rip),%xmm0
40047d:       00 
40047e:       48 83 c4 08             add    $0x8,%rsp
400482:       e9 59 ff ff ff          jmpq   4003e0 

我怀疑该向量等效于标量:

*( int * )&float_value = int_value;

你怎么解释这种行为?

3 个答案:

答案 0 :(得分:9)

这就是定义要执行的矢量转换(其他任何东西都会完全疯狂,并且会使标准的矢量编程习惯用起来非常痛苦)。如果你想真正获得转换,你可能想要使用某种类型的内在函数,比如_mm_cvtepi32_ps(这会打破你的矢量代码的漂亮的架构独立性,当然,这也很烦人;一种常见的方法是使用一个定义便携式“内在函数”的翻译标题。)

为什么这有用?各种原因,但这是最大的原因:

在矢量代码中,您几乎不想分支。相反,如果您需要有条件地执行某些操作,则评估条件的两侧,并使用蒙版来选择适当的结果。这些掩码矢量“自然地”具有整数类型,而您的数据矢量通常是浮点数;你想要使用逻辑运算来组合这两者。如果向量转换只是重新解释这些位,这种非常常见的习语是最自然的。

当然,可以解决这个案例,或任何一些其他常见的矢量习语,但“矢量是一个包位”视图是非常常见的,并反映了大多数矢量程序员的想法。

答案 1 :(得分:2)

事实上,在你的情况下甚至没有生成单个向量指令,甚至在运行时也没有执行类型转换。由于-O3切换,所有这些都在编译时完成。四条MOVSD指令实际上是将预转换的参数加载到printf。实际上,根据SysV AMD64 ABI,浮点参数在XMM寄存器中传递。您已反汇编的部分是(通过使用-S编译获得的汇编代码):

    movsd   .LC6(%rip), %xmm3
    movl    $.LC5, %edi
    movsd   .LC7(%rip), %xmm2
    movl    $4, %eax
    movsd   .LC8(%rip), %xmm1
    movsd   .LC9(%rip), %xmm0
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp     printf
    .cfi_endproc

.LC5标记格式字符串:

.LC5:
    .string "%f %f %f %f\n"

指向格式字符串的指针属于类INTEGER,因此传递到RDI寄存器(位于VA空间的前4 GiB中的某个位置,通过发出一个代码字节来保存) 32位移动到RDI的下半部分。寄存器RAX(用于保存代码字节的EAX)加载了XMM寄存器中传递的参数个数(同样根据SysV AMD64 ABI调用具有可变数量参数的函数)。所有四个MOVSD(MOVe标量双精度)都移动XMM寄存器中的相应参数。 .LC9例如标记了两个双字:

    .align 8
.LC9:
    .long   0
    .long   916455424

这两个形成64位四字0x36A0000000000000,在64位IEEE 754表示中恰好是2 -149 。在非规范化的32位IEEE 754中,它看起来像0x00000001,所以实际上它是整数1的无转换(但由于printf期望double参数,它仍然是预转换的精度加倍)。第二个论点是:

    .align 8
.LC8:
    .long   0
    .long   917504000

这是64位IEEE 754中的0x36B0000000000000或2 -148 和非规范化32位IEEE 754中的0x00000002。其他两个相同参数。

请注意,上面的代码不使用单个堆栈变量 - 它仅使用预先计算的常量进行操作。这是因为使用了非常高的优化级别(-O3)。如果使用较低的优化级别(-O2或更低)进行编译,则会发生实际的运行时转换。然后发出以下代码以执行类型转换:

    movaps  -16(%rbp), %xmm0
    movaps  %xmm0, -32(%rbp)

这只是将四个整数值移动到浮点向量的相应槽中,因此无论如何都不会进行转换。然后对每个元素执行一些SSE mumbo-jumbo,以便将其从单精度转换为双精度(如printf所预期的那样):

    movss   -20(%rbp), %xmm0
    unpcklps        %xmm0, %xmm0
    cvtps2pd        %xmm0, %xmm3

(为什么不只是使用CVTSS2SD超出了我对SSE指令集的理解)

答案 2 :(得分:2)

您可以通过直接遍历元素来从int转换为float

float4 cast(int4 x) {
    float4 y;
    for(int i=0; i<4; i++) y[i] = x[i];
    return y;
}

GCC,Clang和ICC都会为此生成一条指令cvtdq2ps xmm0, xmm0

https://godbolt.org/g/KU1aPg