我很难理解SSE内在函数将SIMD计算结果存储回“正常变量”中的用法。例如,_mm_store_ps内部函数在《英特尔内部函数指南》中描述如下:
void _mm_store_ps(float * mem_addr,__ m128 a)
存储128位(由4个压缩的单精度(32位)组成 浮点元素)从内存到内存。 mem_addr必须对齐 在16个字节的边界上,否则一般保护例外可能是 生成。
第一个参数是一个指向浮点数的指针,该浮点数的大小为32位。但是描述指出,内部函数会将128位从a复制到目标mem_addr。
这是一个简单的结构,其中doSomething()将1加到结构的x / y。缺少的部分是如何将结果存储回x / y中,而只使用32位宽较高的元素2和3,而未使用1和0。
struct vec2 {
union {
struct {
float data[2];
};
struct {
float x, y;
};
};
void doSomething() {
__m128 v1 = _mm_setr_ps(x, y, 0, 0);
__m128 v2 = _mm_setr_ps(1, 1, 0, 0);
__m128 result = _mm_add_ps(v1, v2);
// ?? How to store results in x,y ??
}
}
答案 0 :(得分:0)
这是一个128位加载或存储,所以是的,arg类似于float mem[4]
。请记住,在C语言中,将数组传递给函数/内部函数与传递指针相同。
Intel的内在函数有些特殊,因为它们不遵循正常的严格混叠规则,至少对于整数而言。 (例如,_mm_loadu_si128((const __m128i*)some_pointer)
即使指向long
的指针也不会违反严格混叠。我认为这同样适用于float / double load / store内部函数,因此您可以放心地使用它们来加载/可以存储/存储任何所需的内容。通常,您会使用_mm_load_ps
加载单精度FP位模式,但是通常会将它们保留在float
类型的C对象中。>
如何只访问a中的特定32位元素并将其存储在单个float中?
使用向量随机播放,然后使用_mm_cvtss_f32
将向量转换为标量。
理想情况下,您可以一次将两个向量打包在一起,也可以对一个X值数组和一个Y值数组进行操作,因此,使用一对向量,您将获得4对XY坐标的X和Y值。 (数组结构而不是结构数组)。
但是您可以像这样表达您想要有效地做的事情:
struct vec2 {
float x,y;
};
void foo(const struct vec2 *in, struct vec2 *out) {
__m128d tmp = _mm_load_sd( (const double*)in ); //64-bit zero-extending load with MOVSD
__m128 inv = _mm_castpd_ps(tmp); // keep the compiler happy
__m128 result = _mm_add_ps(inv, _mm_setr_ps(1, 1, 0, 0) );
_mm_storel_pi( out, result );
}
GCC 8.2 compiles it like this (on Godbolt),对于x86-64 System V,奇怪的是使用movq
而不是movsd
进行加载。 gcc 6.3使用movsd
。
foo(vec2 const*, vec2*):
movq xmm0, QWORD PTR [rdi] # 64-bit integer load
addps xmm0, XMMWORD PTR .LC0[rip] # packed 128-bit float add
movlps QWORD PTR [rsi], xmm0 # 64-bit store
ret
对于向量低半部分(2 float
或1 double
)的64位存储,可以使用_mm_store_sd
。或更好的_mm_storel_pi
(movlps
)。不幸的是,它的内在函数需要一个__m64*
arg而不是float*
,但这只是Intel内在函数的设计怪癖。他们经常需要强制类型转换。
请注意,我使用_mm_load_sd((const double*)&(in->x))
而不是_mm_setr
来执行64位加载,该加载零扩展到128位向量。您不希望加载movlps
,因为它会合并到现有向量中。那将对以前的任何值产生虚假的依赖关系,并且会产生额外的ALU成本。