我正在尝试对一些极其性能关键的代码进行矢量化。在较高级别,每个循环迭代从小数组中的非连续位置读取六个浮点数,然后将这些值转换为双精度并将它们添加到六个不同的双精度累加器。这些累加器在迭代中是相同的,因此它们可以存在于寄存器中。由于算法的性质,使存储器访问模式连续是不可行的。尽管如此,该阵列足够小以适应L1缓存,因此内存延迟/带宽不是瓶颈。
我愿意使用汇编语言或SSE2内在函数来并行化。我知道我需要一次将两个浮点数加载到XMM寄存器的两个低位双字中,使用cvtps2pd
将它们转换为两个双精度数,然后使用addpd
一次将它们添加到两个累加器中。
我的问题是,如果它们在内存中彼此不相邻,如何将两个浮点数放入单个XMM寄存器的两个低位字中?显然,任何技术都是如此之慢以至于无法实现并行化的目的并没有用。 ASM或英特尔/ GCC内在函数的答案将不胜感激。
编辑:
严格来说,浮点数组的大小在编译时是不知道的,但几乎总是256,所以这可以是特殊的。
应该读取的float数组的元素是通过从字节数组加载值来确定的。有六个字节数组,每个累加器一个。来自字节数组的读取是顺序的,每个循环迭代的每个数组都有一个读取,所以那里不应该有很多缓存未命中。
浮点数组的访问模式实际上是随机的。
答案 0 :(得分:3)
对于这种特定情况,请查看指令参考手册中的解包和交错指令。这就像是
movss xmm0, <addr1>
movss xmm1, <addr2>
unpcklps xmm0, xmm1
另请查看shufps
,只要您想要的数据顺序错误,这就很方便。
答案 1 :(得分:0)
我认为使用Agner Fog's Vector Class Library中的lookup
函数查看其效果会很有趣。它不是一个你需要编译和链接的库。它只是一个头文件集合。如果将头文件放入源代码目录中,则应编译以下代码。下面的代码一次从六个字节数组中的每一个加载16个字节,将它们扩展为32位整数(因为lookup
函数需要),然后为六个累加器中的每一个收集浮点数。您也可以将此扩展到AVX。我不知道这是否会有更好的表现(可能会更糟)。我的猜测是,如果有一个常规模式,它可以提供帮助(在这种情况下,gather
函数会更好)但无论如何它值得一试。
#include "vectorclass.h"
int main() {
const int n = 16*10;
float x[256];
char b[6][n];
Vec4f sum[6];
for(int i=0; i<6; i++) sum[i] = 0;
for(int i=0; i<n; i+=16) {
Vec4i in[6][4];
for(int j=0; j<6; j++) {
Vec16c b16 = Vec16uc().load(&b[j][i]);
Vec8s low,high;
low = extend_low(b16);
high = extend_high(b16);
in[j][0] = extend_low(low);
in[j][1] = extend_high(low);
in[j][2] = extend_low(high);
in[j][3] = extend_high(high);
}
for(int j=0; j<4; j++) {
sum[0] += lookup<256>(in[0][j], x);
sum[1] += lookup<256>(in[1][j], x);
sum[2] += lookup<256>(in[2][j], x);
sum[3] += lookup<256>(in[3][j], x);
sum[4] += lookup<256>(in[4][j], x);
sum[5] += lookup<256>(in[5][j], x);
}
}
}