【发布时间】:2019-05-12 07:51:22
【问题描述】:
【问题讨论】:
【问题讨论】:
在低和高 128 位通道中有 2 个通道内 haddps 指令。 大多数 AVX 指令并没有真正将操作扩展到 256 位,它们执行 2 个单独的通道内操作。这使得 AVX 难以使用,尤其是没有 AVX2 用于小于 128 位粒度的车道交叉洗牌!
但它节省了晶体管,例如使vpshufb 成为单个 32 字节的随机播放,而不是 2 个 16 字节的随机播放。 AVX2 甚至没有提供:Where is VPERMB in AVX2?(必须等待 AVX512VBMI)。
(相关:best way to shuffle across AVX lanes? 此外,AVX512 增加了许多灵活的车道交叉洗牌,但 AXV512 版本的 SSE/AVX 指令如vhaddps zmm 仍然在车道内。另见Do 128bit cross lane operations in AVX512 give better performance?)
AVX2 vpack* 链通常需要 vpermq 在最后进行车道交叉修复,除非您要再次打开车道内包装。 所以在大多数情况下,2x 通道内随机播放比完整的 256 位宽操作更糟糕,但这不是我们从 AVX 中得到的。通常还有一个加速到 256-位向量从 128 上升,即使它需要额外的洗牌来纠正通道内的行为,但这通常意味着即使没有内存瓶颈,它也不是 2 倍的加速。
vpalignr 可能是同一个 shuffle 的 2x 128 位版本本身并不是一个有用的构建块的最令人震惊的例子;我不记得我是否见过使用 2 个单独的通道内字节数据窗口的用例。哦,实际上是的,如果你用 vperm2i128 How to concatenate two vector efficiently using AVX2? (a lane-crossing version of VPALIGNR) 喂它,但通常未对齐的负载在支持 AVX2 的 CPU 上会更好。
(v)haddps 的用例非常有限也许英特尔计划在将haddps 与 SSE3 一起引入后,在某个时候将其制成单指令指令,但这从未发生过。
用例包括转置和添加类型的东西,无论如何您都需要将两个输入随机排列为垂直 addps。例如Most efficient way to get a __m256 of horizontal sums of 8 source __m256 vectors 包括 vhaddps。 (加上 AVX1 vperm2f128 以纠正车道内行为。)
许多人错误地认为它对单个向量的水平求和有好处,但 128 位和 256 位 (v)haddps 都解码为 2x shuffle uop 以为垂直 (v)addps uop 准备输入向量。对于水平总和,每次添加只需要 1 个 shuffle uop。 (Fastest way to do horizontal float vector sum on x86)
首先缩小到 128 位(使用vextractf128 / vaddps)通常是更好的第一步,除非您希望将结果广播到每个元素,并且您不在 AMD CPU(其中 256 位向量操作解码到至少 2 微秒,或更多用于车道交叉洗牌)。 (v)haddps xmm 或整数 vphaddd 如果您正在优化代码大小而不是速度,则可用于水平求和,例如my x86 machine-code answer 关于代码高尔夫问题“计算两个数字的均值”。
AVX 非破坏性目标操作数还消除了多指令指令的一些吸引力。如果没有 AVX,有时您无法避免 movaps 在销毁寄存器之前复制寄存器,因此烘焙 2x shuffle + add into 1 指令实际上确实节省了 uops,而不必手动使用 movaps + shufps。
【讨论】:
与许多 256 位宽的指令一样,高 128 位
vhaddps ymm ymm ymm 只是 128 位宽 vhaddps xmm xmm xmm 的复制粘贴
操作说明。下面的例子表明它是有意义的
以这种复杂的方式定义vhaddps xmm xmm xmm:使用该指令两次
为您提供 4 个xmm 寄存器的水平总和。
/* gcc -m64 -O3 hadd_ex.c -march=sandybridge */
#include<immintrin.h>
#include<stdio.h>
int main(){
float tmp[4];
__m128 a = _mm_set_ps(1.0, 2.0, 3.0, 4.0);
__m128 b = _mm_set_ps(10.0, 20.0, 30.0, 40.0);
__m128 c = _mm_set_ps(100.0, 200.0, 300.0, 400.0);
__m128 d = _mm_set_ps(1000.0, 2000.0, 3000.0, 4000.0);
__m128 sum1 = _mm_hadd_ps(a, b);
__m128 sum2 = _mm_hadd_ps(c, d);
__m128 sum = _mm_hadd_ps(sum1, sum2);
_mm_storeu_ps(tmp,sum);
printf("sum = %f %f %f %f\n", tmp[0], tmp[1], tmp[2], tmp[3]);
return 0;
}
输出:
sum = 10.000000 100.000000 1000.000000 10000.000000
【讨论】: