将__m256值设置为所有ONE位的最快方法

6
如何在 __m256 值中将所有位设置为 1? 使用 AVX 或 AVX2 intrinsics?
要获得所有零,可以使用 _mm256_setzero_si256()
要获得所有的 1,我目前使用的是 _mm256_set1_epi64x(-1),但我怀疑这比全零情况要慢。这里是否涉及到内存访问或标量/SSE/AVX切换?
而且我似乎找不到一个简单的位 NOT 操作在 AVX 中?如果有的话,我可以简单地使用 setzero,然后是一个向量 NOT。

4
以前,人们使用“pcmpeqd xmm0, xmm0”来实现此操作,AVX(2)中可能有相应的操作吗? - njuffa
5
@njuffa在AVX2中使用vpcmpeqd。Clang似乎会将_mm256_set1_epi64x(-1); 优化为与_mm256_cmpeq_epi64(_mm256_setzero_si256(), _mm256_setzero_si256());相同的操作。 - Dan Mašek
5
请参见:https://dev59.com/wlsW5IYBdhLWcg3wVmGV - harold
1
请查看Agner Fog的《面向x86平台的优化指南》中第13.8节“生成常量”。(链接:https://www.agner.org/optimize/optimizing_assembly.pdf) - phuclv
1个回答

13

请参见高效地将CPU寄存器中的所有位设置为1,该文章涵盖了AVX、AVX2和AVX512 zmm和k(mask)寄存器。


显然你甚至没有查看汇编输出,这是很容易做到的:

#include <immintrin.h>
__m256i all_ones(void) { return _mm256_set1_epi64x(-1); }

编译使用GCC和clang,任何包含AVX2的-march都可以

    vpcmpeqd        ymm0, ymm0, ymm0
    ret

要获取一个__m256(而不是__m256i),您只需要将结果转换即可:
  __m256 nans = _mm256_castsi256_ps( _mm256_set1_epi32(-1) );

如果没有AVX2,一个可能的选择是 vcmptrueps dst, ymm0,ymm0 最好使用冷寄存器来输入以减轻假依赖。

最近的clang(5.0及以上版本)在AVX2不可用时对向量进行异或零操作,然后使用TRUE谓词进行vcmpps。旧版本的clang使用vpcmpeqd xmm生成128位全为1,然后使用vinsertf128。GCC从内存加载,即使是带有-march=sandybridge的现代GCC 10.1也是如此。


正如Agner Fog的优化汇编指南中向量部分所描述的那样,以这种方式动态生成常数是比较便宜的。虽然仍需要一个向量执行单元来生成全1(不同于_mm_setzero),但它比任何可能的两条指令序列都要好,通常也比加载更好。另请参见标签wiki。

编译器不喜欢动态生成更复杂的常量,即使可以通过简单移位从全一生成。即使您尝试使用__m128i float_signbit_mask = _mm_srli_epi32(_mm_set1_epi16(-1), 1)编写,编译器通常也会进行常量传播并将向量放入内存。这使得它们可以在后续使用中将其折叠为内存操作数,在没有循环来提升常量的情况下。
我在AVX中找不到简单的按位取反操作怎么办?
你可以使用 vxorps (_mm256_xor_ps) 与全1进行异或操作来实现。不幸的是,SSE/AVX没有提供一种无需向量常量进行NOT操作的方法。

FP vs 整数指令和旁路延迟

英特尔 CPU(至少 Skylake)存在一种奇怪的效应,即在 SIMD-整数和 SIMD-FP 之间的额外旁路延迟仍会在产生寄存器的 uop 执行后很长时间发生。例如,如果 ymm0 是由 vpcmpeqd 产生的,则 vmulps ymm1, ymm2, ymm0 的关键路径 ymm2 -> ymm1 可能会有一个额外的时钟周期的延迟。 如果您不覆盖 ymm0,则此延迟会持续到下一个上下文切换恢复 FP 状态。

对于像 vxorps 这样的位操作指令来说,这不是问题(即使助记符中有 ps,在 Skylake 上它也没有来自 FP 或 vec-int 域的旁路延迟,如果我没记错的话)。

因此,通常可以使用整数指令创建 set1(-1) 常量,因为它是 NaN,并且您通常不会将其与 FP 数学指令(如 mul 或 add)一起使用。


你也可以按照以下方式生成一个 NOT:not_a = _mm256_andnot_ps(a, all_ones); - ChipK
@ChipK:我记得你最近也做了同样的事情,这就是为什么我抱怨的原因。如果那是另一个用户,那就算了。在发布评论之前尽量把它写完整。意外是难免的,但不要故意这样做。如果我在SO上,当评论通知弹出时,我通常会立即查看它,以便在该人还在场时回复。无论如何,ANDN也可以工作,但是您必须记住哪个操作数是被NOTed的,并且它不能作为负载(只有非内存操作数可以被NOTed;它不是可交换的)。 - Peter Cordes
无论如何,感谢指出ANDN。但由于它仍然需要一个全为1的向量,并且与XOR相比没有任何优势,我认为不值得建议作为考虑的替代方案。我不知道有些人是否会觉得它更易读。但对我来说,带有1的XOR是立即可理解的。 - Peter Cordes
抱歉,我试图在我的文本和代码之间添加回车符,结果添加了注释 - 这是一个简单的错误(添加注释和添加答案之间的区别)。顺便说一下,我不认为你之前指出的是我。 - ChipK
2
@PeterCordes 关于“编译器不喜欢即时生成更复杂的常量,即使可以通过简单移位从全1生成” 我正在LLVM上处理它。这比人们想象的要复杂一些,但已在望。 - Noah
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接