- 可跨 gcc/clang/ICC/MSVC、C 和 C++ 移植。
- 在所有优化级别下完全安全:无严格混叠违规 UB
- 以十六进制打印为 u8、u16、u32 或 u64 元素(基于@AG1 的回答)
- 按内存顺序打印(最不重要的元素在前,如
_mm_setr_epiX
)。如果您希望以英特尔手册使用的相同顺序打印,则反转数组索引,其中最重要的元素位于左侧(如_mm_set_epiX
)。相关:显示向量寄存器的约定
使用 a__m128i*
从数组加载int
是安全的,因为__m128
类型被定义为允许别名,就像 ISO C 一样unsigned char*
。(例如在 gcc 的标题中,定义包括__attribute__((may_alias))
.)
反过来是不安全的(指向对象的int*
一部分__m128i
)。MSVC 保证这是安全的,但 GCC/clang 不保证。(-fstrict-aliasing
默认开启)。它有时适用于 GCC/clang,但为什么要冒险呢?它有时甚至会干扰优化;看到这个问答。另请参阅硬件 SIMD 向量指针和相应类型之间的“重新解释转换”是否是未定义的行为?
(uint32_t*) &my_vector
违反了 C 和 C++ 别名规则,并且不能保证按您期望的方式工作。存储到本地数组然后访问它可以保证是安全的。它甚至可以优化大多数编译器,例如,您可以直接从 xmm 获取movq
/pextrq
到整数寄存器,而不是实际的存储/重新加载。
Godbolt 编译器资源管理器上的 Source + asm 输出:证明它可以使用 MSVC 等进行编译。
#include <immintrin.h>
#include <stdint.h>
#include <stdio.h>
#ifndef __cplusplus
#include <stdalign.h> // C11 defines _Alignas(). This header defines alignas()
#endif
void p128_hex_u8(__m128i in) {
alignas(16) uint8_t v[16];
_mm_store_si128((__m128i*)v, in);
printf("v16_u8: %x %x %x %x | %x %x %x %x | %x %x %x %x | %x %x %x %x\n",
v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7],
v[8], v[9], v[10], v[11], v[12], v[13], v[14], v[15]);
}
void p128_hex_u16(__m128i in) {
alignas(16) uint16_t v[8];
_mm_store_si128((__m128i*)v, in);
printf("v8_u16: %x %x %x %x, %x %x %x %x\n", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
}
void p128_hex_u32(__m128i in) {
alignas(16) uint32_t v[4];
_mm_store_si128((__m128i*)v, in);
printf("v4_u32: %x %x %x %x\n", v[0], v[1], v[2], v[3]);
}
void p128_hex_u64(__m128i in) {
alignas(16) unsigned long long v[2]; // uint64_t might give format-string warnings with %llx; it's just long in some ABIs
_mm_store_si128((__m128i*)v, in);
printf("v2_u64: %llx %llx\n", v[0], v[1]);
}
如果您需要移植到 C99 或 C++03 或更早版本(即没有 C11/C++11),请删除并alignas()
使用storeu
. store
或使用__attribute__((aligned(16)))
or__declspec( align(16) )
代替。
(如果您使用内在函数编写代码,则应该使用最新的编译器版本。较旧的编译器通常比较旧的编译器生成更好的 asm,包括 SSE/AVX 内在函数。但也许您想将 gcc-6.3 与-std=gnu++03
C++03一起使用未准备好 C++11 或其他东西的代码库的模式。)
调用所有 4 个函数的示例输出
// source used:
__m128i vec = _mm_setr_epi8(1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 16);
// output:
v2_u64: 0x807060504030201 0x100f0e0d0c0b0a09
v4_u32: 0x4030201 0x8070605 0xc0b0a09 0x100f0e0d
v8_u16: 0x201 0x403 0x605 0x807 | 0xa09 0xc0b 0xe0d 0x100f
v16_u8: 0x1 0x2 0x3 0x4 | 0x5 0x6 0x7 0x8 | 0x9 0xa 0xb 0xc | 0xd 0xe 0xf 0x10
如果要使用前导零填充以获得一致的输出宽度,请调整格式字符串。见printf(3)
。