【发布时间】:2021-10-26 13:48:43
【问题描述】:
如标题所示 - 我想做如下:
__m128i_u* avxVar = (__m128i_u*)Var; // Var allocated with alloc
*avxVar = _mm_set_epi64(...); // is that ok to assign __m128i to __m128i_u ?
【问题讨论】:
标签: simd sse intrinsics sse2
如标题所示 - 我想做如下:
__m128i_u* avxVar = (__m128i_u*)Var; // Var allocated with alloc
*avxVar = _mm_set_epi64(...); // is that ok to assign __m128i to __m128i_u ?
【问题讨论】:
标签: simd sse intrinsics sse2
是的,但请注意 __m128i_u 不可移植(例如,可移植到 MSVC);这是 GCC/clang 在内部使用来实现未对齐的 loadu/storeu 内在函数。 完全等同于以正常方式进行:
_mm_storeu_si128((__m128i*)Var, vec);
(其中vec 是任何__m128i。例如,它可以是_mm_set_epi64x 或变量。)
GCC 11 的 emmintrin.h 实现 _mm_storeu_si128 是这样定义的,采用 __m128i_u* 指针 arg,因此取消引用会进行未对齐的访问(如果未优化)。
// GCC internals, quoted for reference only.
// Just use #include <immintrin.h> in your own code
extern __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_storeu_si128 (__m128i_u *__P, __m128i __B)
{
*__P = __B;
}
所以是的,GCC 的标头取决于 __m128i* 和 __m128i_u* 是否兼容且可隐式转换。
_mm_storeu_si128 是 movdqu 的内在属性,__m128i_u* 取消引用也是如此。但实际上这些内在函数只是为了向编译器传达对齐信息而存在,由编译器决定何时实际加载和存储,就像 char* 的 deref 一样。
(有趣的事实:__m128i* 是一个may_alias 类型,就像char*,所以你可以在不违反严格别名的情况下将它指向任何东西。Is `reinterpret_cast`ing between hardware SIMD vector pointer and the corresponding type an undefined behavior?)
还请注意,_mm_set_epi64 采用 __m64 参数:它用于从两个 MMX 向量构建 SSE2 向量,而不是从标量 int64_t。你可能想要_mm_set_epi64x
void foo(void *Var) {
__m128i_u* avxVar = (__m128i_u*)Var;
*avxVar = _mm_set_epi64x(1, 2);
}
void bar(void *Var) {
_mm_storeu_si128((__m128i*)Var, _mm_set_epi64x(1, 2) );
}
这两个函数在 gcc/clang/MSVC 中编译相同(并且在语义上是等效的,因此在内联后总是相同)。
但是只有第二个使用 MSVC 编译,正如您在 Godbolt 编译器资源管理器中看到的那样:https://godbolt.org/z/Y8Wq96Pqs。如果禁用#ifdef __GNUC__,则会在 MSVC 上出现编译器错误。
## GCC -O3
foo:
movdqa xmm0, XMMWORD PTR .LC0[rip]
movups XMMWORD PTR [rdi], xmm0
ret
bar:
movdqa xmm0, XMMWORD PTR .LC0[rip]
movups XMMWORD PTR [rdi], xmm0
ret
.LC0:
.quad 2
.quad 1
对于更复杂的周围代码,_mm_loadu_si128 可以折叠到仅用于 AVX 的 ALU 的内存源操作数(例如 vpaddb xmm0, xmm1, [rdi],但 _mm_load_si128 对齐的负载可以折叠到 SSE 内存源中,例如 paddb xmm0, [rdi]。
【讨论】:
_mm_loadu_si128 实现中。 (这很有效。它只是让 C 编译器发出未对齐的负载/存储的一种机制,请参阅更新的答案。在您发表评论之前,我已经编写了一些更新。)
vmovdqa / vmovdqu 有点令人惊讶的是只需要 AVX1,所以是的,如果您有没有 AVX2 的 AVX1(例如 Sandybridge 或 Bulldozer),您可以使用 _mm256_storeu_si256。如果您没有 AVX2,通常情况下您只需将 _mm256_storeu_ps 与 AVX1 一起使用。但请注意,所有没有 AVX2 的现有 CPU 实际上会将 256 位加载/存储作为两个 16 位半,而 SnB 对未对齐有额外的惩罚。
__m256 或 __m256i。当您编译时,请注意调整选项-mavx256-split-unaligned-load,不幸的是默认情况下:Why doesn't gcc resolve _mm256_loadu_pd as single vmovupd?