问题描述
基本上,假设您在编译时有一个置换索引列表,我试图了解x86_64的最佳指令选择顺序。
我了解大多数Agner Fog's优化选择,但有一种情况我难以理解。
给出可以实现为一个的排列顺序;
_mm256_permutevar8x32_epi32(r,_mm256_set_epi32(/* indicies */));
或
__m256i tmp = _mm256_permute4x64_epi64(r,/* some mask */);
return _mm256_shuffle_epi32(tmp,/* another mask */);
我不明白为什么第一个选择会更好。
以排列列表7,6,5,4,3,2,1,0
(反向epi32)为例
__m256i
load_perm(__m256i r) {
// clang
// 1 uop vmovaps (y,m) p23
// 1 uop vpermps (y,y,y) p5
// gcc
// 1 uop vmovdqa (y,m) p23
// 1 uop vpermd (y,y) p5
return _mm256_permutevar8x32_epi32(r,_mm256_set_epi32(0,7));
}
__m256i
perm_shuf(__m256i r) {
// clang
// 1 uop vmovaps (y,y) p5
// gcc
// 1 uop vpermq (y,i) p5
// 1 uop vpshufd (y,i) p5
__m256i tmp = _mm256_permute4x64_epi64(r,0x4e);
return _mm256_shuffle_epi32(tmp,0x1b);
}
两个选项都需要2个uop,并且鉴于这两个指令之间存在依赖关系,我认为端口并不重要。我看到的唯一区别是,第一个选项添加了额外的32个字节的.rodata。
有人能帮我理解为什么Clang(我猜是Agner Fog)为什么偏爱第一个而不是第二个吗?
here是与skylake的编译输出一起的Godbolt链接
解决方法
对于load_perm
,c语似乎想将事物变成ps
形式。这样可以节省传统SSE编码的代码大小(其中SSE1指令的前缀更少)。但是不是具有VEX编码,因此没有任何上升空间。只是clang的shuffle优化器显然不知道或不希望保留整数与FP域的区别。我认为这对于当前CPU上的改组是很好的。
对于perm_shuf
,这绝对是clang的shuffle优化器。其他编译器不太善于像对待+
和*
运算符一样对待随机内在函数:作为指定所需结果的方法,而无需则必须指定如何到达那里。例如对于x86,x * y
不必编译为imul
,选择取决于周围的代码。
大多数SIMD代码都是循环运行的,因此可以很好地假设洗牌常量将在高速缓存中保持高温并被多次使用。特别是如果可以内联并且可以重新排列随机向量。但是即使没有,也值得加载一个常量。 对于从m
输入到return
值的关键路径的延迟,以及英特尔CPU上的port-5 oups(通常限制为1个随机播放),一次随机播放优于2次随机播放从哈斯韦尔开始,直到冰湖。)
顺便说一句,m
是一个非常差的变量名选择:它到达寄存器,并且您在注释中使用m
来谈论内存常量。