填充__m128i参数的正确方法是啥,从基本类型(例如short)到与_mm256_broadcast_epi(例如_mm_broadcastw_epi16)一起使用

Posted

技术标签:

【中文标题】填充__m128i参数的正确方法是啥,从基本类型(例如short)到与_mm256_broadcast_epi(例如_mm_broadcastw_epi16)一起使用【英文标题】:What is the correct way to fill a __m128i parameter, from basic type (such as short), to use with _mm256_broadcast_epi (such as _mm_broadcastw_epi16)填充__m128i参数的正确方法是什么,从基本类型(例如short)到与_mm256_broadcast_epi(例如_mm_broadcastw_epi16)一起使用 【发布时间】:2020-11-04 10:23:39 【问题描述】:

所有四个 _mm256_broadcastb_epi8、_mm_broadcastw_epi16、_mm256_broadcastd_epi32 和 _mm256_broadcastq_epi64 函数是 VPBROADCASTB、VPBROADCASTW、VPBROADCASTD 和 VPBROADCASTQ 指令的内在函数。 根据英特尔的文档:"Intel® Advanced Vector Extensions Programming Reference", 这些指令可能会相应地接收 8 位、16 位、32 位、64 位内存位置。 第 5-230 页:

源操作数是8位,16位32位, 64位内存位置或XMM寄存器中的低8位、16位、32位、64位数据

但是,这些指令的内部 API(Intel、MSVS 和 gcc)接收 __m128i 参数。 现在,如果我有一个基本类型的变量,据说是“短”,那么最有效和跨平台的方式是什么(至少在 MSVS 和 gcc 之间) 将该变量传递给相应的广播内在函数(_mm_broadcastw_epi16 在短的情况下)?

例如:

void func1(uint8_t v) 
    __m256i a = _mm256_broadcastb_epi8(<convert_to__m128i>(v));
    ...


void func1(uint16t v) 
    __m256i a = _mm256_broadcastw_epi16(<convert_to__m128i>(v));
    ...


void func1(uint32_t v) 
    __m256i a = _mm256_broadcastd_epi32(<convert_to__m128i>(v));
    ...


void func1(uint64_t v) 
    __m256i a = _mm256_broadcastq_epi64(<convert_to__m128i>(v));
    ...

应该是什么才能最高效和跨平台(如果可能)?

以 MSVS 为例:

void func1(uint16t v) 
    __m128i vt;
    vt.m128_u16[0] = v;
    __m256i a = _mm256_broadcastw_epi16(vt);
    ...

但如果没有优化,它可以先加载一个 xmm 寄存器,然后才能在 VPBROADCASTW 中使用它。 当进行优化时,它可以直接使用 v 的内存位置。 它也只对 MSVS 有效。

【问题讨论】:

_mm256_set1_epi8 和朋友们。 仅供参考,如果您知道指令的助记符并想知道与其相关的内在函数,那么进行这样的搜索非常有用:software.intel.com/sites/landingpage/IntrinsicsGuide/… _mm_broadcastb_epi8 产生__m128i 结果,而不是__m256i。你通常不想要它,只要让编译器为 _mm256_set1_epi8 内在函数发出广播指令,如果你有一个标量值开始,而不是 __m128i 【参考方案1】:

已经有序列/复合内在函数可以完全满足您的需求:

_mm256_set1_epi8/16/32/64

来自 Intels 内在函数指南:

Broadcast 8-bit integer a to all elements of dst. This intrinsic may generate the vpbroadcastb.

使用那些您应该能够信任编译器生成最佳代码的代码。

在执行此类操作时,我会使用 Intel Intrinsics Guide,这很有帮助,因为您可以从助记符进行反向搜索(在这种情况下,您知道您最终需要 vpbroadcastb),它会告诉您哪些内在函数与之相关。

【讨论】:

确实我错过了_m256_set1_epi,它看起来正是我想要的。因此,_mm256_broadcast 似乎适用于您有一个 128 位寄存器 (xmm) 开始的情况。【参考方案2】:

如果您有一个标量值开始,而不是 __m128i,则让编译器为 _mm256_set1_epi8(scalar) 内在函数发出广播指令。


但您通常也不想要 _mm_broadcastb_epi8__m128i 结果)或 __m256i _mm256_broadcastb_epi8(__m128i),除非您已经有一个 __m128i 开头并想要低元素。

如果您不关心低 dword 的高 2 或 3 个字节的高位,那么在编译器不浪费指令的情况下将标量放入 __m128i 可能是 8 位或 16 位标量的实际问题;它可能会将movzx 加载到一个整数寄存器中,然后使用vmovd 或类似的东西。


【讨论】:

我在示例中错误地将 _mm_broadcastb 而不是 _mm256_broadcast。确实我错过了 _m256_set1_epi,它看起来正是我想要的。

以上是关于填充__m128i参数的正确方法是啥,从基本类型(例如short)到与_mm256_broadcast_epi(例如_mm_broadcastw_epi16)一起使用的主要内容,如果未能解决你的问题,请参考以下文章

打印 _mm_cmpeq_epi8 的输出(__m128i 类型)

如何将 16 字节的内存加载到 Rust __m128i 中?

SSE:如何将 _m128i._i32[4] 减少到 _m128i._i8

从 __m128i 中查找最小值/最大值

如何从 16 x 8 位 __m128i 值中提取 32 x 4 位整数

清除 __m128i 的高字节