将单个浮点数移动到 xmm 寄存器

Posted

技术标签:

【中文标题】将单个浮点数移动到 xmm 寄存器【英文标题】:Moving a single float to a xmm register 【发布时间】:2011-11-27 14:34:13 【问题描述】:

我想将存储在一个 xmm 寄存器中的数据与一个浮点值相乘,并将结果保存在一个 xmm 寄存器中。 我做了一个小图来更好地解释它。

如您所见,我有一个 xmm0 寄存器,其中包含我的数据。例如它包含:

xmm0 = |4.0|2.5|3.5|2.0|

每个浮点数存储在 4 个字节中。我的 xmm0 寄存器是 128 位,16 字节长。

效果很好。现在我想将 0.5 存储在另一个 xmm 寄存器中,例如xmm1,并将此寄存器与 xmm0 寄存器相乘,使 xmm0 中存储的每个值都乘以 0.5。

我完全不知道如何在 XMM 寄存器中存储 0.5。 有什么建议吗?

顺便说一句:它是 C++ 中的内联汇编程序。

void filter(image* src_image, image* dst_image)

    float* src = src_image->data;
    float* dst = dst_image->data;

    __asm__ __volatile__ (              
        "movaps (%%esi), %%xmm0\n"      
        // Multiply %xmm0 with a float, e.g. 0.5
        "movaps %%xmm0, (%%edi)\n" 

        :
        : "S"(src), "D"(dst) :  
    );

这是我想做的事情的安静简单版本。我将一些图像数据存储在浮点数组中。指向这些数组的指针被传递给程序集。 movaps 获取数组的前 4 个浮点值,将这 16 个字节存储在 xmm0 寄存器中。在此之后 xmm0 应该乘以例如0.5。比“新”值应存储在 edi 的数组中。

【问题讨论】:

现在最好使用内在函数。这样您的代码就独立于编译器,并且您可以获得自动寄存器分配。 【参考方案1】:

正如人们在 cmets 中指出的那样,对于这种非常简单的操作,使用内在函数本质上总是更好:

void filter(image* src_image, image* dst_image)

    const __m128 data = _mm_load_ps(src_image->data);
    const __m128 scaled = _mm_mul_ps(data, _mm_set1_ps(0.5f));
    _mm_store_ps(dst_image->data, scaled);

只有在编译器生成错误代码时(并且仅在向编译器供应商提交错误后),您才应该使用内联 ASM。

如果你真的想留在汇编中,有许多方法来完成这项任务。您可以在 ASM 块之外定义一个比例向量:

    const __m128 half = _mm_set1_ps(0.5f);

然后像使用其他操作数一样在 ASM 中使用它。

如果你真的想这样做,你可以在没有任何负担的情况下做到这一点:

    "mov    $0x3f000000, %%eax\n"  // encoding of 0.5
    "movd   %%eax,       %%xmm1\n" // move to xmm1
    "shufps $0, %%xmm1,  %%xmm1\n" // splat across all lanes of xmm1

这只是两种方法。还有很多其他的方法。您可能会花一些时间阅读英特尔指令集参考。

【讨论】:

+1 带有立即值的 MOVD 比我的版本好得多,我的版本只能从内存中加载。没有考虑过使用整数运算。 @ChristianRau:我不认为我会说它“好多了”;这将取决于周围的环境。它们只是不同的方法。 我认为常量可能会更好,因为它直接来自指令缓存,您不必从静态内存中获取它。但你说得对,这取决于我,反正我不是机器专家。 @Copa:我刚好知道这个值;我写了很多低级的 FP 代码。 IEEE-754 单精度数具有 8 位指数字段和 23 位有效数字字段。指数场的偏差是127。所以1.0f = 2^0127 + 0 << 23,或者0x3f8000000.5f = 2^-1127 - 1 << 23,也就是 0x3f000000。普通人可能更喜欢使用babbage.cs.qc.edu/IEEE-754 =P @Christian:从通用寄存器加载 xmm 寄存器可能会导致比 L1 缓存延迟大得多的惩罚,具体取决于具体的硬件。此外,任何延迟都可以通过重新排序来隐藏。【参考方案2】:

假设您使用的是内部函数:__m128 halfx4 = _mm_set1_ps(0.5f);

编辑:

使用内在函数会更好:

__m128 x = _mm_mul_ps(_mm_load_ps(src), halfx4);
_mm_store_ps(dst, x);

如果 srcdst 浮点数据不是 16 字节对齐的,则需要:_mm_loadu_ps_mm_storeu_ps - 速度较慢。

【讨论】:

我可以在内联汇编器中使用它吗? 您可以将其作为操作数传递。你用的是什么编译器? 我正在使用 gcc for linux。 “将其作为操作数传递”是什么意思? 您需要显示内联汇编块 - 特别是它的输入和输出。【参考方案3】:

您正在寻找 MOVSS 指令(它将单精度浮点数从内存加载到 SSE 寄存器的最低 4 个字节中),然后是随机播放以使用此值填充其他 3 个浮点数:

movss  (whatever), %%xmm1
shufps %%xmm1, %%xmm1, $0

_mm_set1_ps 内在函数可能也是这样做的。然后,您可以将这些 SSE 值相乘或做任何您想做的事情:

mulps %%xmm1, %%xmm0

【讨论】:

【参考方案4】:

如果你使用 c++ 和 gcc 并且有 EasySSE 你的代码可以如下

void filter(float* src_image, float* dst_image)
    *(PackedFloat128*)dst_image =  Packefloat128(0.5) * (src_image+0);

这是假设给定的指针是 16 字节对齐的。 您可以检查组件代码以验证变量是否正确映射到向量寄存器。

【讨论】:

【参考方案5】:

这是一种方法:

#include <stdio.h>
#include <stdlib.h>

typedef struct img 
    float *data;
 image_t;

image_t *src_image;
image_t *dst_image;
void filter(image_t*, image_t*);

int main()

    image_t src, dst;
    src.data = malloc(64);
    dst.data = malloc(64);
    src_image=&src;
    dst_image=&dst;

    *src.data = 42.0;
    filter(src_image, dst_image);

    printf("%f\n", *dst.data);
    free(src.data);
    free(dst.data);
    return 0;


void filter(image_t* src_image, image_t* dst_image)

    float* src = src_image->data;
    float* dst = dst_image->data;

    __asm__ __volatile__ (              
        "movd   %%esi, %%xmm0;"
        "movd   %%xmm0, %%edi;"
        : "=D" (*dst)
        : "S" (*src)
    );

【讨论】:

以上是关于将单个浮点数移动到 xmm 寄存器的主要内容,如果未能解决你的问题,请参考以下文章

如何将单精度浮点数的 XMM 寄存器转换为整数?

有没有办法用 xor 翻转 32 位浮点数的符号位?

如何在 x86(32 位)程序集中将无符号整数转换为浮点数?

将常量浮点数加载到 SSE 寄存器中

使用 SSE 将 4 个浮点数乘以 4 个浮点数的最有效方法是啥?

为什么浮点寄存器与通用寄存器不同