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

4
我想把一个XMM寄存器中存储的数据与单精度浮点值相乘,并将结果保存在另一个XMM寄存器中。我画了一张小图来更好地解释一下。
如您所见,我有一个XMM0寄存器,其中包含我的数据。例如,它包含:
xmm0 = |4.0|2.5|3.5|2.0|
每个浮点数都以4个字节存储。我的xmm0寄存器是128位,16个字节长。
这很好用。现在我想在另一个XMM寄存器(例如XMM1)中存储0.5,并将该寄存器与xmm0寄存器相乘,以便将存储在xmm0中的每个值乘以0.5。
我完全不知道如何将0.5存储在XMM寄存器中。您有什么建议吗?
顺便说一下:这是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
现在最好使用内置函数。这样你的代码就不依赖于编译器,而且可以获得自动寄存器分配。 - Axel Gneiting
5个回答

8

在评论中有人指出,对于这种非常简单的操作,最好使用内置函数:

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块外定义一个比例向量:

    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

这只是两种方法,还有很多其他方法。您可以花些时间查看英特尔指令集参考资料。


3
@Copa: 我碰巧知道这个值;我写了很多底层的FP代码。IEEE-754单精度浮点数具有8位指数字段和23位有效数字字段。指数字段的偏置为127。因此, 1.0f = 2^0127 + 0 << 23,或者是 0x3f800000;而 0.5f = 2^-1127 - 1 << 23,即 0x3f000000。普通人可能更喜欢使用http://babbage.cs.qc.edu/IEEE-754/ =P - Stephen Canon
@Christian:从通用寄存器加载xmm寄存器可能会产生比L1缓存延迟更大的惩罚,这取决于具体的硬件。此外,任何延迟都可以通过重新排序来隐藏。 - Gunther Piez
@drhirsch: movd自从Core以来,在每个微架构上都是单周期操作。我认为现在并没有很多高性能代码针对Netburst。话虽如此,你提到的重新排序是非常准确的,并且与我的观点差不多。 - Stephen Canon
我更多地是指即使在不那么老的Core i(Nehalem)架构中,也存在数据绕过延迟。在Agner Fogs微架构pdf中,我相信你知道,它被列为1个额外周期(在执行端口下),当然它是一个指令(mov immediate+movd vs. mov mem,xmm)更多,这可能会阻塞指令解码器。有时候我确实会在某些地方计算每个周期,我发现即使在现代架构上,mov mem,xmm 也可能更可取(在其他情况下则不是:-))。但你当然是对的,它总是取决于周围的上下文。 - Gunther Piez
@drhirsch:我知道有三种情况,通过这种方式产生FP值是一个净胜利:(1)如果你完全受延迟的限制(由于依赖链太长,重排序缓冲区填满并且没有任何好处)-即使使用Core i*旁路延迟,延迟也较小(2)如果您在32位上需要加载一个或两个常量,则可以节省放弃寄存器用于PIC基地址(并且节省两个指令来生成该地址)(3)如果您的工作集恰好填充L1 D $,则可以防止缺失。所有这些都是边角案例。 - Stephen Canon
显示剩余3条评论

4
假设您正在使用指令集:__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函数进行处理。但这两个函数的速度较慢。请留意HTML标签。

我可以在内联汇编中使用这个吗? - Basic Coder
你可以将它作为操作数传递。你使用的是哪个编译器? - Brett Hale
我在Linux上使用gcc。你说的“将其作为操作数传递”是什么意思? - Basic Coder
您需要展示内联汇编块 - 特别是它的输入和输出。 - Brett Hale

2
您需要使用MOVSS指令(将单精度浮点数从内存加载到SSE寄存器的最低4个字节),然后使用洗牌指令将此值填充到其他3个浮点数中。
movss  (whatever), %%xmm1
shufps %%xmm1, %%xmm1, $0

那也是 `_mm_set1_ps` 内在函数可能会这样做的方式。然后你可以乘以这些 SSE 值或者进行任何你想要的操作。
mulps %%xmm1, %%xmm0

0
这是一种实现方式:
#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)
    );
}

0
如果您正在使用带有gcc的c++并且拥有EasySSE,则您的代码可以如下所示:
void filter(float* src_image, float* dst_image){
    *(PackedFloat128*)dst_image =  Packefloat128(0.5) * (src_image+0);
}

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接