为什么vhaddps指令以这种复杂的方式添加?

时间:2019-05-12 07:51:23

标签: assembly x86 avx

vhaddps指令以一种非常特殊的方式添加:

enter image description here

来源:https://www.felixcloutier.com/x86/haddps

这是什么原因?该说明适用于哪些用例?看起来设计中有一些特定的想法。

2 个答案:

答案 0 :(得分:2)

这是低和高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添加了许多灵活的跨车道改组功能,但诸如vhaddps zmm之类的AXV512版本的SSE / AVX指令仍在车道内。另请参阅{{3 }})

一串AVX2 vpack*通常需要一个vpermq才能在最后进行车道交叉固定,除非您要再次拆开车道内的行李。 因此,在大多数情况下,2倍的通道内混洗比完整的256位宽操作要差,但这不是我们从AVX获得的结果。从256-即使需要额外的改组来纠正车道内行为,位向量也可以从128位提高到12位,但这通常意味着即使没有内存瓶颈,它也不是2倍加速。

vpalignr可能是同一洗牌的2x 128位版本最令人震惊的例子,它本身并不是有用的构建块;我不记得我是否曾经见过用例来获取2个单独的行内字节数据窗口。哦,实际上是的,如果您使用vperm2i128 Do 128bit cross lane operations in AVX512 give better performance?来填充它,但是通常在未支持AVX2的CPU上未对齐的负载会更好。


(v)haddps的用例非常有限

也许Intel计划在将SSE3引入haddps到某个时候成为单uup指令,但这从未发生。

用例包括转置和添加类型的事物,无论如何,您都需要对垂直addps的两个输入进行混洗。例如How to concatenate two vector efficiently using AVX2? (a lane-crossing version of VPALIGNR)包括vhaddps。 (加上AVX1 vperm2f128可以纠正车道内行为。)

许多人错误地认为这对于单个矢量的水平和很有用,但是128位和256位(v)haddps都解码为2x随机混码,以为垂直(v)addps uop准备输入矢量。对于水平和,您每次添加仅需要1个shuffle uop。 (Most efficient way to get a __m256 of horizontal sums of 8 source __m256 vectors

除非您希望将结果广播到每个元素,并且您不在AMD CPU上(其中,将vextractf128 / vaddps)缩小到128位通常是更好的第一步。 256位向量运算至少解码为2 oups,对于车道交叉混洗,至少要解码2 oups。如果您针对代码大小而不是速度进行优化,例如,(v)haddps xmm或整数vphaddd对于水平和很有用。 Fastest way to do horizontal float vector sum on x86关于代码高尔夫球问题“计算两个数字的均值”。

AVX非破坏性目标操作数还消除了使用多uup指令的某些吸引力。如果没有AVX,有时您无法避免movaps在销毁寄存器之前先对其进行复制,因此将2x shuffle + add in 1指令烘焙实际上节省了uops,而必须手动使用movaps + shufps

答案 1 :(得分:2)

与许多256位宽指令一样,高128位 android { compileSdkVersion 28 defaultConfig { applicationId "com.errorfoundteam.gmaillogintest" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'com.google.android.gms:play-services-auth:16.0.1' implementation 'androidx.core:core-ktx:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'com.google.firebase:firebase-auth:17.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } apply plugin: 'com.google.gms.google-services' 的其中一个只是128位宽vhaddps ymm ymm ymm的副本粘贴 指令。以下示例说明了 以一种涉及的方式定义vhaddps xmm xmm xmm:两次使用此指令 给您4个vhaddps xmm xmm xmm寄存器的水平总和。

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;
}