将单个浮点数移动到 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^0
是127 + 0 << 23
,或者0x3f800000
; 0.5f = 2^-1
是 127 - 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);
如果 src
和 dst
浮点数据不是 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 寄存器的主要内容,如果未能解决你的问题,请参考以下文章
如何在 x86(32 位)程序集中将无符号整数转换为浮点数?