如何在256位AVX(YMM)寄存器中交换低128位和高128位?

28
我正在将SSE SIMD代码移植为使用256位AVX扩展,并且似乎找不到可以混合/洗牌/移动高128位和低128位的任何指令。
背景故事:
我真正想要的是VHADDPS / _mm256_hadd_psHADDPS/_mm_hadd_ps一样工作,只是用256位字。不幸的是,它的行为类似于对低字和高字分别独立调用两次HADDPS

如果您只想进行水平求和,通常您会使用vextractf128,它在任何地方(特别是Zen1)都很快,缩小到128位向量。如何水平求和__m256?。但是,您不希望haddps成为高效水平求和的一部分,所以希望这不是您要做的...除非您有多个hsums要执行,那么是的,vhaddps可以像Intel AVX:双精度浮点变量的256位版本点积中那样有用。也许还有2x vperm2f128 + vaddps。 - Peter Cordes
3个回答

31

使用VPERM2F128,可以交换低128位和高128位(以及其他排列方式)。它的内部函数用法如下:

x = _mm256_permute2f128_ps( x , x , 1)

第三个参数是一个控制词,它为用户提供了很大的灵活性。有关详细信息,请参见Intel Instrinsic Guide


英特尔参考手册指定了控制字:VPERM2F128(直接链接) - AVX2还有VPERM2I128,基本上是相同的 - 不知道为什么英特尔觉得他们需要两个不同的指令,因为类型不应该有区别,或者应该吗? - maxschlepzig
1
valignq也可用于对512位进行与64位增量的等效 ROR(使用valignd可获得32位)。 - Alexis Wilke
@AlexisWilke:这需要AVX-512。只使用AVX2,您可以使用立即的vpermq来交换单个向量的一半。 vperm2f128仅需要AVX1,但在某些CPU上比vpermq慢(例如,Zen1和KNL)。 - Peter Cordes

4
x = _mm256_permute4x64_epi64(x, 0b01'00'11'10);

这里阅读相关内容。并且在线尝试!

注意:此指令需要AVX2(而不仅仅是AVX1)。

正如@PeterCordes在Zen2 / Zen3 CPU上的速度方面所评论的那样,_mm256_permute2x128_si256(x,x,i)是最佳选择,即使它有3个参数,而我建议使用2个参数的函数_mm256_permute4x64_epi64(x,i)

在Zen1和KNL/KNM(以及Bulldozer家族的Excavator)上,由我提出的_mm256_permute4x64_epi64(x, i)更加高效。 在其他CPU上(包括主流Intel),两种选择都是相等的。

正如已经说过的,_mm256_permute2x128_si256(x,y,i)_mm256_permute4x64_epi64(x,i)都需要AVX2,而_mm256_permute2f128_si256(x,i)只需要AVX1。


3
这需要AVX2而不仅仅是AVX1,但是在某些CPU上比VPERM2F128更快,在其他CPU上相同。(包括Zen1(出人意料的https://uops.info/),以及Knight's Landing,其中2输入洗牌较慢)。除了只有AVX1的CPU无法运行它的Sandybridge和Piledriver之外,我认为它在任何地方都不会更差。 - Peter Cordes
当然,将这个答案包含在这个问题中是很好的,但重要的是提醒人们内在函数需要哪个扩展名,特别是当它超出了使用涉及类型的最小扩展名或问题中提到的扩展名时。 (该问题正在使用FP,并且__m256可以完全与AVX1一起使用。您无法使用__m256i做太多事情,而vpermpd此洗牌的版本也是AVX2,就像所有其他小于128位的粒度的横跨车道的洗牌一样)。 - Peter Cordes
2
是的,确切地说,在Zen2 / Zen3上, _mm256_permute2x128_si256(x,x,i)是最佳选择,需要将相同的输入重复两次。在Zen1和KNL / KNM(以及Bulldozer家族Excavator)中, _mm256_permute4x64_epi64(x,i)更有效率。在其他CPU上(包括主流英特尔),这两个选择都是相等的。AVX1 CPU没有选择,只有vperm2f128可用。即使vpermpd也是AVX2。 - Peter Cordes
2
vperm2f128(AVX1)和vperm2i128(AVX2)在每个AVX2 CPU上都运行相同。 我认为在使用f128版本的AVX2整数指令之间没有任何真实CPU使用额外的旁路延迟,但最好使用i128版本 - 它不应该比vperm2f128更差,尽管它可能比取决于CPU的vpermq更差。 - Peter Cordes
1
两者在任何地方都以相同的速度运行 - 这是我不确定的事情。例如,在vpaddb ymm,ymm指令之间使用vperm2f128可能会导致某些CPU具有额外的延迟。因此,如果您正在使用其他需要AVX2的__m256i内部函数,请使用_mm256_permute2x128_si256_mm256_permute4x64_epi64。如果您在只需要AVX1(也许是FMA)的函数中使用__m256__m256d,那么除了为了vpermpd而制作单独的AVX2版本(除非您想要专门针对Zen1进行调整(考虑其128位向量硬件)),否则没有必要。 - Peter Cordes
显示剩余5条评论

3

我所知道的唯一方法是使用_mm256_extractf128_si256_mm256_set_m128i。例如,要交换一个256位向量的两个半部分:

__m128i v0h = _mm256_extractf128_si256(v0, 0);
__m128i v0l = _mm256_extractf128_si256(v0, 1);
__m256i v1 = _mm256_set_m128i(v0h, v0l);

2
你知道 "_mm256_extractf128_si256" 和 "_mm256_extracti128_si256" 之间的区别吗? 我能说的唯一一件事是,第一个使用 AVX,而第二个需要 AVX2。 为什么有人会使用第二个版本呢?我查看了Agner Fog的指令表,延迟,吞吐量和端口都是相同的。也许我应该把这个问题问出来。 - Z boson
1
我认为我已经在SO上看到过这个问题,但快速搜索没有找到 - 据我所知,它们的效果是相同的。 - Paul R
@Zboson:糟糕 - 我刚刚找到了我上面提到的问题 - 我应该搜索指令而不是内部函数:https://dev59.com/d2Mk5IYBdhLWcg3wxAhM - Paul R
我认为这种方式比Mark的答案慢,因为 extractfset 每个操作都具有延迟 3,吞吐量 1。 - mafu
1
@mafu:是的,没错 - 还要注意的是,clang(以及其他编译器)足够聪明,可以将上述内容转换为单个vperm2f128,从而使其本质上与Mark的答案相同。 - Paul R
@PaulR 感谢您的澄清! - mafu

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