【发布时间】:2018-12-25 05:00:15
【问题描述】:
Intel 提供了一个名为 _mm256_madd_epi16 的 C 风格函数,它基本上是
__m256i _mm256_madd_epi16 (__m256i a, __m256i b)
将 a 和 b 中的压缩有符号 16 位整数相乘,生成中间有符号 32 位整数。将相邻的中间 32 位整数对水平相加,并将结果打包到 dst 中。
现在我有两个 __m256i 变量,每个变量都有 32 个 8 位 int。
我想实现与 _mm256_madd_epi16 相同的功能,但结果 __m256i 中的每个 int32_t 元素是 四个带符号字符的乘积之和,而不是两对带符号的 int16_t。
我可以在标量循环中做到这一点:
alignas(32) uint32_t res[8];
for (int i = 0; i < 32; ++i)
res[i / 4] += _mm256_extract_epi8(a, i) * _mm256_extract_epi8(b, i);
return _mm256_load_si256((__m256i*)res);
请注意,相乘结果是 sign-在相加之前扩展为 int,并且 _mm256_extract_epi8 辅助函数1returns signed __int8。没关系,总数是uint32_t 而不是int32_t;它无论如何都不会溢出,只需要添加四个 8x8 => 16 位数字。
它看起来很丑陋,并且运行效率不高,除非编译器使用 SIMD 做一些魔法,而不是按照写入标量提取的方式进行编译。
脚注 1:_mm256_extract_epi8 不是内在的。 vpextrb 仅适用于 256 位向量的低通道,并且此辅助函数可能允许索引不是编译时常量。
【问题讨论】:
-
请注意,
int不是固定大小的整数类型。如果您想要 32 位无符号类型,请明确使用uint32_t。 -
@Someprogrammerdude:所有实现 Intel 内在函数的编译器都有 32 位
int,因此在使用内在函数的代码中假设int并没有错。_mm_cvtsi128_si32(movd) and many other intrinsics actually returnint和其他使用intarg 而不是int32_t。通过以这种方式指定内在函数,Intel 基本上需要一个 ABI,其中int是(至少?)32 位的才能支持它们。综上所述,如果我是这个意思,我通常使用int32_t。 -
@AmorFati:您可以将
pmaddubsw用作您的用例的构建块吗?这是一个垂直乘法/水平加法,如pmaddwd,但有一个输入被认为是无符号的。所以它可能不适用于您的用例。 -
您的示例实现没有意义。你的意思是
+=而不是=?因为在只保留每组第 4 个字节的结果之前,您目前有 3 个死存储。此外,您使用uint32_t*访问不同类型的对象存在严格别名冲突。只有char*和__m256i*/__m128*/ 其他__m... *指针可以安全地为其他对象起别名。 -
你可能应该问一个关于调试的不同问题,显示一个完整的minimal reproducible example,你会得到不同的结果。不要用调试来混淆这个问题,留下这个关于优化一对向量的问题。
标签: c++ x86 simd intrinsics avx2