C++ SSE 内在函数:将结果存储在变量中
C++ SSE Intrinsics: Storing results in variables
我无法理解 SSE 内在函数将某些 SIMD 计算的结果存储回 "normal variables" 的用法。例如,_mm_store_ps 内在函数在 "Intel Intrinsics Guide" 中的描述如下:
void _mm_store_ps (float* mem_addr, __m128 a)
Store 128-bits (composed of 4 packed single-precision (32-bit)
floating-point elements) from a into memory. mem_addr must be aligned
on a 16-byte boundary or a general-protection exception may be
generated.
第一个参数是一个指向大小为 32 位的浮点数的指针。但是描述指出,内在函数会将 128 位从 a 复制到目标 mem_addr.
- mem_addr 必须是 4 个浮点数的数组吗?
- 如何只访问 a 中的特定 32 位元素并将其存储在单个浮点数中?
- 我在概念上缺少什么?
- 有比 _mm_store_ps 内在更好的选择吗?
这是一个简单的结构,其中 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 ??
}
}
这是一个 128 位加载或存储,所以是的,arg 就像 float mem[4]
。请记住,在 C 中,将数组传递给函数/内在函数与传递指针相同。
Intel 的内在函数有些特殊,因为它们不遵循正常的严格别名规则,至少对于整数而言。 (例如 _mm_loadu_si128((const __m128i*)some_pointer)
不违反严格别名,即使它是指向 long
的指针。我认为这同样适用于 float/double load/store 内在函数,因此您可以安全地使用它们到 load/store from/to 任何你想要的。通常你会使用 _mm_load_ps
来加载单精度 FP 位模式,并且通常你会把它们保存在 [= 类型的 C 对象中16=],虽然。
How can I access only a specific 32bit element in a and store it in a single float?
使用向量随机播放,然后 _mm_cvtss_f32
将向量转换为标量。
加载/存储 64 位
理想情况下,您可以同时对两个打包在一起的向量进行操作,或者对一个 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 系统 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
对于向量低半部分的 64 位存储(2 float
或 1 double
),您可以使用 _mm_store_sd
。或者更好 _mm_storel_pi
(movlps
)。不幸的是,它的内在函数需要 __m64*
arg 而不是 float*
,但这只是英特尔内在函数的设计怪癖。他们通常需要类型转换。
请注意,我使用 _mm_load_sd((const double*)&(in->x))
而不是 _mm_setr
来执行零扩展到 128 位向量的 64 位加载。您不需要 movlps
加载,因为它会合并到现有向量中。这将对之前的任何值产生错误的依赖,并花费额外的 ALU uop。
我无法理解 SSE 内在函数将某些 SIMD 计算的结果存储回 "normal variables" 的用法。例如,_mm_store_ps 内在函数在 "Intel Intrinsics Guide" 中的描述如下:
void _mm_store_ps (float* mem_addr, __m128 a)
Store 128-bits (composed of 4 packed single-precision (32-bit) floating-point elements) from a into memory. mem_addr must be aligned on a 16-byte boundary or a general-protection exception may be generated.
第一个参数是一个指向大小为 32 位的浮点数的指针。但是描述指出,内在函数会将 128 位从 a 复制到目标 mem_addr.
- mem_addr 必须是 4 个浮点数的数组吗?
- 如何只访问 a 中的特定 32 位元素并将其存储在单个浮点数中?
- 我在概念上缺少什么?
- 有比 _mm_store_ps 内在更好的选择吗?
这是一个简单的结构,其中 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 ??
}
}
这是一个 128 位加载或存储,所以是的,arg 就像 float mem[4]
。请记住,在 C 中,将数组传递给函数/内在函数与传递指针相同。
Intel 的内在函数有些特殊,因为它们不遵循正常的严格别名规则,至少对于整数而言。 (例如 _mm_loadu_si128((const __m128i*)some_pointer)
不违反严格别名,即使它是指向 long
的指针。我认为这同样适用于 float/double load/store 内在函数,因此您可以安全地使用它们到 load/store from/to 任何你想要的。通常你会使用 _mm_load_ps
来加载单精度 FP 位模式,并且通常你会把它们保存在 [= 类型的 C 对象中16=],虽然。
How can I access only a specific 32bit element in a and store it in a single float?
使用向量随机播放,然后 _mm_cvtss_f32
将向量转换为标量。
加载/存储 64 位
理想情况下,您可以同时对两个打包在一起的向量进行操作,或者对一个 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 系统 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
对于向量低半部分的 64 位存储(2 float
或 1 double
),您可以使用 _mm_store_sd
。或者更好 _mm_storel_pi
(movlps
)。不幸的是,它的内在函数需要 __m64*
arg 而不是 float*
,但这只是英特尔内在函数的设计怪癖。他们通常需要类型转换。
请注意,我使用 _mm_load_sd((const double*)&(in->x))
而不是 _mm_setr
来执行零扩展到 128 位向量的 64 位加载。您不需要 movlps
加载,因为它会合并到现有向量中。这将对之前的任何值产生错误的依赖,并花费额外的 ALU uop。