我正在尝试优化一些代码,我的状态是有4个向量,我想将它们的总和存储在另一个向量中。所以基本上,结果=[求和(a),求和(b),求和(c),求和(d)]。我知道有一种方法可以做到这一点,使用2个hadd a混合和置换,但我意识到hadd太贵了。
所以我想知道是否有一种内在的机制可以让我们更快地做到这一点。
三个选项:
好:概念简单,使用通用算法(矩阵转置),可移植代码
错误:代码大小、延迟、吞吐量
vhaddpd
好:代码小(适用于Icache),在Intel UARCH上具有良好的延迟和吞吐量
坏:需要架构特定的代码,在一些uArch上有问题
好:延迟好,吞吐量好
错误:没有vhaddpd代码那么小,没有完整的矩阵转置那么容易理解
让您的编译器为此进行优化。使用gcc
向量扩展*,在转置矩阵上求和的代码可能如下所示:
#include <stdint.h>
typedef uint64_t v4u64 __attribute__((vector_size(32)));
typedef double v4f64 __attribute__((vector_size(32)));
v4f64 dfoo(v4f64 sv0, v4f64 sv1, v4f64 sv2, v4f64 sv3)
{
v4f64 tv[4];
tv[0] = __builtin_shuffle(sv0, sv1, (v4u64){0,4,2,6});
tv[1] = __builtin_shuffle(sv0, sv1, (v4u64){1,5,3,7});
tv[2] = __builtin_shuffle(sv2, sv3, (v4u64){0,4,2,6});
tv[3] = __builtin_shuffle(sv2, sv3, (v4u64){1,5,3,7});
v4f64 fv[4];
fv[0] = __builtin_shuffle(tv[0], tv[2], (v4u64){0,1,4,5});
fv[1] = __builtin_shuffle(tv[0], tv[2], (v4u64){2,3,6,7});
fv[2] = __builtin_shuffle(tv[1], tv[3], (v4u64){0,1,4,5});
fv[3] = __builtin_shuffle(tv[1], tv[3], (v4u64){2,3,6,7});
return fv[0]+fv[1]+fv[2]+fv[3];
}
gcc-9.2.1
生成以下程序集:
dfoo:
vunpcklpd %ymm3, %ymm2, %ymm5
vunpcklpd %ymm1, %ymm0, %ymm4
vunpckhpd %ymm1, %ymm0, %ymm0
vinsertf128 $1, %xmm5, %ymm4, %ymm1
vperm2f128 $49, %ymm5, %ymm4, %ymm4
vunpckhpd %ymm3, %ymm2, %ymm2
vaddpd %ymm4, %ymm1, %ymm1
vinsertf128 $1, %xmm2, %ymm0, %ymm3
vperm2f128 $49, %ymm2, %ymm0, %ymm0
vaddpd %ymm3, %ymm1, %ymm1
vaddpd %ymm0, %ymm1, %ymm0
ret
艾格纳·福格的表格上写着:
在所有,有
port5上的吞吐量会出现瓶颈。大约18个周期的延迟非常糟糕。代码大小约为60字节。
通过gcc向量扩展,使用vhadd的代码(合理地)不容易获取,因此代码需要特定于英特尔的内部函数:
v4f64 dfoo_hadd(v4f64 sv0, v4f64 sv1, v4f64 sv2, v4f64 sv3)
{
v4f64 hv[2];
hv[0] = __builtin_ia32_haddpd256(sv0, sv1); //[00+01, 10+11, 02+03, 12+13]
hv[1] = __builtin_ia32_haddpd256(sv2, sv3); //[20+21, 30+31, 22+23, 32+33]
v4f64 fv[2];
fv[0] = __builtin_shuffle(hv[0], hv[1], (v4u64){0, 1, 4, 5}); //[00+01, 10+11, 20+21, 30+31]
fv[1] = __builtin_shuffle(hv[0], hv[1], (v4u64){2, 3, 6, 7}); //[02+03, 12+13, 22+23, 32+33]
return fv[0] + fv[1]; //[00+01+02+03, 10+11+12+13, 20+21+22+23, 30+31+32+33]
}
这将生成以下组件:
dfoo_hadd:
vhaddpd %ymm3, %ymm2, %ymm2
vhaddpd %ymm1, %ymm0, %ymm0
vinsertf128 $1, %xmm2, %ymm0, %ymm1
vperm2f128 $49, %ymm2, %ymm0, %ymm0
vaddpd %ymm0, %ymm1, %ymm0
ret
根据Agner Fog的说明书,
在所有,有
吞吐量也受到port5的限制,这比转置代码有更多的吞吐量。延迟应该大约为16个周期,也比转置代码快。代码大小约为25字节。
实施@PeterCordes评论:
v4f64 dfoo_PC(v4f64 sv0, v4f64 sv1, v4f64 sv2, v4f64 sv3)
{
v4f64 tv[4];
v4f64 av[2];
tv[0] = __builtin_shuffle(sv0, sv1, (v4u64){0,4,2,6});//[00, 10, 02, 12]
tv[1] = __builtin_shuffle(sv0, sv1, (v4u64){1,5,3,7});//[01, 11, 03, 13]
av[0] = tv[0] + tv[1];//[00+01, 10+11, 02+03, 12+13]
tv[2] = __builtin_shuffle(sv2, sv3, (v4u64){0,4,2,6});//[20, 30, 22, 32]
tv[3] = __builtin_shuffle(sv2, sv3, (v4u64){1,5,3,7});//[21, 31, 23, 33]
av[1] = tv[2] + tv[3];//[20+21, 30+31, 22+23, 32+33]
v4f64 fv[2];
fv[0] = __builtin_shuffle(av[0], av[1], (v4u64){0,1,4,5});//[00+01, 10+11, 20+21, 30+31]
fv[1] = __builtin_shuffle(av[0], av[1], (v4u64){2,3,6,7});//[02+03, 12+13, 22+23, 32+33]
return fv[0]+fv[1];//[00+01+02+03, 10+11+12+13, 20+21+22+23, 30+31+32+33]
}
这会产生:
dfoo_PC:
vunpcklpd %ymm1, %ymm0, %ymm4
vunpckhpd %ymm1, %ymm0, %ymm1
vunpcklpd %ymm3, %ymm2, %ymm0
vunpckhpd %ymm3, %ymm2, %ymm2
vaddpd %ymm1, %ymm4, %ymm1
vaddpd %ymm2, %ymm0, %ymm2
vinsertf128 $1, %xmm2, %ymm1, %ymm0
vperm2f128 $49, %ymm2, %ymm1, %ymm1
vaddpd %ymm1, %ymm0, %ymm0
ret
在所有,有
这与hadd
-code获得相同数量的port5 uOP。代码在port5上仍然存在瓶颈,延迟约为16个周期。代码大小约为41字节。
如果您想增加吞吐量,您必须将工作从port5转移。不幸的是,几乎所有permute/插入/随机指令都需要port5,而车道交叉指令(此处需要)至少有3个周期延迟。一个几乎有帮助的有趣指令是vblendpd
,它具有3/周期吞吐量,1周期延迟,并且可以在port015上执行,但使用它来替换permute/插入/shuffles之一需要对向量的128位通道进行64位移位,由vpsrldq/vpslldq
实现,您猜对了-需要port5 uOP(因此这将有助于32位浮动
的向量,因为vpsllq/vpsrlq
不需要port5)。这里没有免费的午餐。
*gcc矢量扩展快速描述:
代码使用gcc向量扩展,允许使用基本运算符(-*/=
最初,我的答案是关于uint64\t的,保留在这里作为上下文:
#include <stdint.h>
typedef uint64_t v4u64 __attribute__((vector_size(32)));
v4u64 foo(v4u64 sv0, v4u64 sv1, v4u64 sv2, v4u64 sv3)
{
v4u64 tv[4];
tv[0] = __builtin_shuffle(sv0, sv1, (v4u64){0,4,2,6});
tv[1] = __builtin_shuffle(sv0, sv1, (v4u64){1,5,3,7});
tv[2] = __builtin_shuffle(sv2, sv3, (v4u64){0,4,2,6});
tv[3] = __builtin_shuffle(sv2, sv3, (v4u64){1,5,3,7});
v4u64 fv[4];
fv[0] = __builtin_shuffle(tv[0], tv[2], (v4u64){0,1,4,5});
fv[1] = __builtin_shuffle(tv[0], tv[2], (v4u64){2,3,6,7});
fv[2] = __builtin_shuffle(tv[1], tv[3], (v4u64){0,1,4,5});
fv[3] = __builtin_shuffle(tv[1], tv[3], (v4u64){2,3,6,7});
return fv[0]+fv[1]+fv[2]+fv[3];
}
skylake-avx2上gcc-9.2.1生成的翻译如下:
foo:
vpunpcklqdq %ymm3, %ymm2, %ymm5
vpunpcklqdq %ymm1, %ymm0, %ymm4
vpunpckhqdq %ymm3, %ymm2, %ymm2
vpunpckhqdq %ymm1, %ymm0, %ymm0
vperm2i128 $32, %ymm2, %ymm0, %ymm3
vperm2i128 $32, %ymm5, %ymm4, %ymm1
vperm2i128 $49, %ymm2, %ymm0, %ymm0
vperm2i128 $49, %ymm5, %ymm4, %ymm4
vpaddq %ymm4, %ymm1, %ymm1
vpaddq %ymm0, %ymm3, %ymm0
vpaddq %ymm0, %ymm1, %ymm0
ret
请注意,程序集具有与gcc向量扩展对应的几乎行。
根据Agner Fog为Skylake提供的指令表,
vPunpck[h/l]qdq
:1个周期延迟,每个周期吞吐量1,端口5。
vperm2i128
:3个周期延迟,每个周期吞吐量1个,端口5。 vpaddq
:1个周期延迟,3个周期吞吐量,ports015。
因此,转置需要10个周期(4个周期用于解包,4个周期用于吞吐量,2个周期用于置换)。在三个加法中,只有两个可以并行执行,因此总共需要2个周期,共12个周期。
我运行jmeter脚本将近一周,今天观察到一件有趣的事情。以下是场景: 概述:我正在逐渐增加应用程序的负载。在上一次测试中,我给应用程序加载了100个用户,今天我将加载增加到150个用户。 150名用户测试结果: > 与上次测试相比,请求的响应时间减少了。(这是个好兆头) 吞吐量急剧下降到上一次测试的一半,负载更少。 我的问题是: > 当我的许多请求失败时,我得到了好的响应时间吗? 注:直到100
我在一个应用程序里工作。这个应用程序在Android7.x.x中运行,但当我尝试在Android5.x.x中运行这个应用程序时,这个应用程序崩溃了。我认为这是因为是API25。当我尝试将其更改为API21(Android5)时,我出现了一些错误。我可以在Android5中对我的应用工作做些什么? PS:我不知道这款应用在Android6中是否有效,但很可能是不行的。 Build.Gradle: 执
减少图层数量 初始化图层,处理图层,打包通过IPC发给渲染引擎,转化成OpenGL几何图形,这些是一个图层的大致资源开销。事实上,一次性能够在屏幕上显示的最大图层数量也是有限的。 确切的限制数量取决于iOS设备,图层类型,图层内容和属性等。但是总得说来可以容纳上百或上千个,下面我们将演示即使图层本身并没有做什么也会遇到的性能问题。 裁切 在对图层做任何优化之前,你需要确定你
这里是一个以圆圈为单位的交叉网格,当前为5x5。我试图得到一行5,下面是一行4,然后是3,然后是2等等。我试着改变for循环和值,但什么都不起作用。我需要使用行和列吗? 谢谢!
我对Spring Reactive编程有点陌生。我试图从I/O得到一个Flux,然后返回一个对象列表,以及从我的服务中返回一个Mono要组合什么。 为了实现这一点,我的初始方法是确保通量是在操作数据之后完成的。 但是上面的语句返回了Mono的一个空格,不确定在这种情况下,那么许多人如何工作。 我觉得这里缺少了一些东西,我应该如何在完成后控制Flux对象。
给出这个简化的示例代码: 如何实现reduce操作的结果也是空的?