7

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

手册指出:

可以从一种向量类型转换为另一种向量类型,前提是它们具有相同的大小(实际上,您也可以将向量转换为相同大小的其他数据类型或从其他数据类型转换)。

这是一个简单的例子:

#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 移动 $0x400649,%edi
400461: f2 0f 10 15 17 02 00 movsd 0x217(%rip),%xmm2
400468: 00
400469: b8 04 00 00 00 移动 $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 添加 $0x8,%rsp
400482:e9 59 ff ff ff jmpq 4003e0

我怀疑标量的向量等价物:

*( int * )&float_value = int_value;

你如何解释这种行为?

4

3 回答 3

9

这就是向量转换的定义(其他任何事情都将是完全疯狂的,并且会使标准向量编程习语编写起来非常痛苦)。如果您想实际进行转换,您可能希望使用某种内在函数,例如 _mm_cvtepi32_ps (这会破坏矢量代码的良好架构独立性,当然,这也很烦人;一种常见的方法是使用定义一组可移植的“内在”的翻译头)。

为什么这很有用?原因有很多,但最大的原因如下:

在向量代码中,您几乎从不想分支。相反,如果您需要有条件地做某事,您评估条件的两侧,并使用掩码逐个通道选择适当的结果。这些掩码向量“自然”具有整数类型,而您的数据向量通常是浮点数;您想使用逻辑运算将两者结合起来。如果向量转换只是重新解释位,那么这个极其常见的习语是最自然的。

诚然,可以解决这种情况,或者任何其他常见的向量习语袋,但是“向量是一袋比特”的观点非常普遍,并且反映了大多数向量程序员的思维方式。

于 2012-09-11T18:38:19.480 回答
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)。寄存器RAXEAX用于节省代码字节)加载 XMM 寄存器中传递的参数数量(再次根据 SysV AMD64 ABI 调用具有可变数量参数的函数)。所有四个MOVSD(MOVe Scalar Double-precision)移动 XMM 寄存器中的相应参数。.LC9例如标记两个双字:

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

这两个形成了 64 位四字,在 64 位 IEEE 754 表示0x36A0000000000000中恰好是 2 -149 。在非规范化的 32 位 IEEE 754 中,它看起来像0x00000001,所以确实它不是整数的转换1(但由于printf需要double参数,它仍然被预先转换为双精度)。第二个论点是:

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

这在 64 位 IEEE 754 和非规范化 32 位 IEEE 754 中是0x36B0000000000000or 2 -1480x00000002。对于其他两个参数也是如此。

请注意,上面的代码不使用单个堆栈变量 - 它仅使用预先计算的常量进行操作。这是由于使用了非常高的优化级别 ( -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指令集的理解)

于 2012-09-12T15:05:05.823 回答
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

于 2018-01-18T12:56:44.593 回答