有些问题的标题相似,但我的问题与一个非常特殊的用例有关,其他地方未涉及。
我有4个__128d寄存器(x0,x1,x2,x3),我想重新组合它们在5个__256d寄存器(y0,y1,y2,y3,y4)中的内容,以准备其他计算:
on entry:
x0 contains {a0, a1}
x1 contains {a2, a3}
x2 contains {a4, a5}
x3 contains {a6, a7}
on exit:
y0 contains {a0, a1, a2, a3}
y1 contains {a1, a2, a3, a4}
y2 contains {a2, a3, a4, a5}
y3 contains {a3, a4, a5, a6}
y4 contains {a4, a5, a6, a7}
我在下面的实现很慢。有没有更好的办法?
y0 = _mm256_set_m128d(x1, x0);
__m128d lo = _mm_shuffle_pd(x0, x1, 1);
__m128d hi = _mm_shuffle_pd(x1, x2, 1);
y1 = _mm256_set_m128d(hi, lo);
y2 = _mm256_set_m128d(x2, x1);
lo = hi;
hi = _mm_shuffle_pd(x2, x3, 1);
y3 = _mm256_set_m128d(hi, lo);
y4 = _mm256_set_m128d(x3, x2);
最佳答案
使用寄存器中的输入,您可以按照5个随机播放指令进行操作:
vinsertf128
,通过串联2个xmm寄存器来创建y0,y2和y4。 vshufpd
(车道内混洗)来创建y1和y3。 请注意,y0和y2的低 channel 包含a1和a2,这是y1的低 channel 所需的元素。相同的混洗也适用于高车道。
#include <immintrin.h>
void merge(__m128d x0, __m128d x1, __m128d x2, __m128d x3,
__m256d *__restrict y0, __m256d *__restrict y1,
__m256d *__restrict y2, __m256d *__restrict y3, __m256d *__restrict y4)
{
*y0 = _mm256_set_m128d(x1, x0);
*y2 = _mm256_set_m128d(x2, x1);
*y4 = _mm256_set_m128d(x3, x2);
// take the high element from the first vector, low element from the 2nd.
*y1 = _mm256_shuffle_pd(*y0, *y2, 0b0101);
*y3 = _mm256_shuffle_pd(*y2, *y4, 0b0101);
}
很好地编译(with gcc and clang
-O3 -march=haswell
on Godbolt)到:merge(double __vector(2), double __vector(2), double __vector(2), double __vector(2), double __vector(4)*, double __vector(4)*, double __vector(4)*, double __vector(4)*, double __vector(4)*):
vinsertf128 ymm0, ymm0, xmm1, 0x1
vinsertf128 ymm3, ymm2, xmm3, 0x1
vinsertf128 ymm1, ymm1, xmm2, 0x1
# vmovapd YMMWORD PTR [rdi], ymm0
vshufpd ymm0, ymm0, ymm1, 5
# vmovapd YMMWORD PTR [rdx], ymm1
vshufpd ymm1, ymm1, ymm3, 5
# vmovapd YMMWORD PTR [r8], ymm3
# vmovapd YMMWORD PTR [rsi], ymm0
# vmovapd YMMWORD PTR [rcx], ymm1
# vzeroupper
# ret
我注释掉了存储和内联的东西,所以我们确实只用了5条随机指令,而您问题中的代码只有9条随机指令。 (也包含在Godbolt编译器资源管理器链接中)。
这在AMD上非常好,因为
vinsertf128
是 super 便宜的(因为256位寄存器实现为2x 128位一半,因此它只是128位拷贝,不需要特殊的洗牌端口。)256位 channel 交叉在AMD上,随机播放速度很慢,但是像vshufpd
这样的车道内256位随机播放只有2微秒。在Intel上,这是相当不错的,但是带有AVX的主流Intel CPU在256位或FP混洗中每个时钟的混洗吞吐量仅为1。 (Sandybridge和更早版本的整数128位洗牌具有更高的吞吐量,但是AVX2 CPU放弃了多余的洗牌单元,无论如何,它们都无济于事。)
因此,英特尔CPU根本无法利用指令级并行性,但是总共只有5微妙。这是最小的可能,因为您需要5个结果。
但是尤其是如果周围的代码也出现洗牌瓶颈,值得考虑只有4个存储和5个重叠 vector 加载的存储/重载策略。或者也许是2x
vinsertf128
来构造y0
和y4
,然后是2x 256位存储+ 3个重叠的重载。这可能会使乱序的exec仅使用y0
或y4
来开始依赖指令的处理,而将存储转发停顿解析为y1..3。尤其是如果您不太在意英特尔第一代Sandybridge,那么未对齐的256位 vector 加载效率会降低。 (请注意,如果您使用的是GCC,则希望使用
gcc -mtune=haswell
进行编译以关闭-mavx256-split-unaligned-load
的默认设置/sandybridge调整。无论使用哪种编译器,如果使二进制文件在要编译的计算机上运行,-march=native
都是一个好主意。 ,以充分利用指令集和设置调整选项。)但是,如果前端的总uop吞吐量更多地位于瓶颈所在,那么洗牌实现是最好的。
(有关性能调优的更多信息,请参见https://agner.org/optimize/和x86 tag wiki中的其他性能链接。还有What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?,但实际上Agner Fog的指南是更深入的指南,它解释了吞吐量与延迟之间的关系。)
I do not even need to save, as data is also already available in contiguous memory.
然后,简单地将其加载5次重叠的加载几乎可以肯定是您可以执行的最有效的操作。
Haswell的每个时钟从L1d可以完成2次加载,当任何一次跨过高速缓存行边界时,Haswell可以执行更少的加载。 因此,如果您可以将块对齐64,则完全没有缓存行拆分的效果非常好。 高速缓存未命中的速度很慢,但是从L1d高速缓存重新加载热数据的成本非常低,并且具有AVX支持的现代CPU通常具有有效的未对齐加载支持。
(就像我之前说的,如果使用gcc,请确保使用
-march=haswell
或-mtune=haswell
编译,而不仅仅是-mavx
,以避免使用gcc的-mavx256-split-unaligned-load
。)根据周围代码中的瓶颈,4个载荷+ 1个
vshufpd
(y0,y2)可能是平衡载荷端口压力和ALU压力的一种好方法。如果周围的代码在混洗端口压力低的情况下,甚至3个载荷+ 2个混洗。they are in registers from previous calculations which required them to be loaded.
如果该先前的计算仍将源数据保存在寄存器中,则您可以首先进行256位加载,而仅将其128位低半用于较早的计算。 (XMM寄存器是相应的YMM寄存器的低128,读取它们不会干扰高 channel ,因此
_mm256_castpd256_pd128
编译为0个asm指令。)对y0,y2和y4进行256位加载,并将它们的下半部分用作x0,x1和x2。 (稍后用未对齐的负载或混洗构造y1和y3)。
仅x3还不是您还想要的256位 vector 的低128位。
理想情况下,当您从同一地址执行
_mm_loadu_pd
和_mm256_loadu_pd
时,编译器已经注意到这种优化,但是您可能需要通过执行此操作来保持它的优劣。__m256d y0 = _mm256_loadu_pd(base);
__m128d x0 = _mm256_castpd256_pd128(y0);
依此类推,并根据周围的代码提取ALU内部(
_mm256_extractf128_pd
)或x3
的128位负载。如果只需要一次,则最好将其折叠为任何使用它的指令的内存操作数。潜在的不利方面:128位计算开始之前的等待时间会稍长一些;如果256位负载是高速缓存行交叉而没有128位负载,则延迟几个周期。但是,如果您的数据块按64字节对齐,则不会发生这种情况。
关于c++ - 跨AVX channel 进行改组的最佳方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52982211/