【问题标题】:How to cast SIMD int vectors to float in GCC?如何将 SIMD int 向量转换为在 GCC 中浮动?
【发布时间】:2012-09-04 16:55:19
【问题描述】:

我在一个项目中使用 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 移动 $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 个非规范化浮点数而不是实际值转换。
  • 这就是向量转换的定义(其他任何事情都将是完全疯狂的,并且会使标准向量编程习语编写起来非常痛苦)。如果您想实际进行转换,您可能希望使用某种内在函数,例如_mm_cvtepi32_ps(这会破坏矢量代码的良好架构独立性,当然,这也很烦人;一种常见的方法是使用定义一组可移植的“内在”的翻译头)。
  • 我明白你的意思,但问题变成了:这个 cast 什么时候有用?
  • @cYrus 信不信由你,我实际上使用这个(按位)转换比值转换更频繁。
  • @StephenCanon 如果您想将该评论转化为答案,我很乐意接受。

标签: c gcc vectorization simd


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案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 位四字 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指令集的理解)

    【讨论】:

    • 感谢您的澄清!
    【解决方案3】:

    您可以通过直接循环元素来将 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-11
      • 1970-01-01
      • 2015-07-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多