是否可以将myNum+;矢量化a[b[i]]*c[i];在x86_64上?
在x86_64上,我将使用什么内部函数对以下内容进行矢量化(如果有可能进行矢量化)是否可以将myNum+;矢量化a[b[i]]*c[i];在x86_64上?,x86,x86-64,sse,simd,vectorization,X86,X86 64,Sse,Simd,Vectorization,在x86_64上,我将使用什么内部函数对以下内容进行矢量化(如果有可能进行矢量化) double myNum = 0; for(int i=0;i<n;i++){ myNum += a[b[i]] * c[i]; //b[i] = int, a[b[i]] = double, c[i] = double } double myNum=0; 对于(int i=0;i短答案否。长答案是肯定的,但效率不高。您将因执行非对齐加载而受到惩罚,这将否定任何类型的好处。除非您能够保证b[i]连
double myNum = 0;
for(int i=0;i<n;i++){
myNum += a[b[i]] * c[i]; //b[i] = int, a[b[i]] = double, c[i] = double
}
double myNum=0;
对于(int i=0;i短答案否。长答案是肯定的,但效率不高。您将因执行非对齐加载而受到惩罚,这将否定任何类型的好处。除非您能够保证b[i]连续索引对齐,否则向量化后的性能很可能会更差
如果您事先知道什么是索引,最好是展开并指定显式索引。我使用模板专门化和代码生成做了类似的事情。如果您感兴趣,我可以与您分享
要回答您的评论,您基本上必须专注于一个数组。立即尝试的最简单方法是将您的循环阻塞两倍,分别加载低a和高a,然后像通常一样使用mm*\U pd。伪代码:
__m128d a, result;
for(i = 0; i < n; i +=2) {
((double*)(&a))[0] = A[B[i]];
((double*)(&a))[1] = A[B[i+1]];
// you may also load B using packed integer instruction
result = _mm_add_pd(result, _mm_mul_pd(a, (__m128d)(C[i])));
}
\uuum128d a,结果;
对于(i=0;i
我记不清函数名,可能需要再次检查。
另外,若您知道可能并没有别名问题,那个么在指针中使用restrict关键字。
这将使编译器更具攻击性。这不会像现在这样进行矢量化,因为数组索引具有双重间接定向。由于使用双重索引,因此从SSE中几乎得不到什么好处,特别是因为大多数现代CPU都有2个FPU。我将从展开循环开始。有些东西艾克
double myNum1=0,myNum2=0;
对于(int i=0;i这是我的做法,经过充分优化和测试:
#包括
__m128d和=_mm_setzero_pd();
对于(int i=0;i英特尔处理器可以执行两个浮点运算,但每个周期只能执行一次加载,因此内存访问是最严格的限制。考虑到这一点,我的目标是首先使用压缩加载来减少加载指令的数量,并使用压缩算法,因为它很方便。此后,我意识到,饱和内存带宽并不会h可能是最大的问题,如果关键是让代码运行得更快,而不是学习矢量化,那么所有与SSE指令有关的混乱可能都是过早的优化
上海证券交易所
在不假设b
中的索引的情况下,尽可能少的加载需要将循环展开四次。一个128位加载从b
获取四个索引,两个128位加载分别从c
获取一对相邻的双精度加载,并收集a
所需的独立64位加载。
这是串行代码每四次迭代7个周期的下限。(如果对a
的访问不能很好地缓存,就足以饱和我的内存带宽)。我省略了一些恼人的事情,比如处理不是4的倍数的迭代次数
entry: ; (rdi,rsi,rdx,rcx) are (n,a,b,c)
xorpd xmm0, xmm0
xor r8, r8
loop:
movdqa xmm1, [rdx+4*r8]
movapd xmm2, [rcx+8*r8]
movapd xmm3, [rcx+8*r8+8]
movd r9, xmm1
movq r10, xmm1
movsd xmm4, [rsi+8*r9]
shr r10, 32
movhpd xmm4, [rsi+8*r10]
punpckhqdq xmm1, xmm1
movd r9, xmm1
movq r10, xmm1
movsd xmm5, [rsi+8*r9]
shr r10, 32
movhpd xmm5, [rsi+8*r10]
add r8, 4
cmp r8, rdi
mulpd xmm2, xmm4
mulpd xmm3, xmm5
addpd xmm0, xmm2
addpd xmm0, xmm3
jl loop
获取索引是最复杂的部分。movdqa
从16字节对齐的地址加载128位整数数据(Nehalem对混合使用“整数”和“浮点”SSE指令有延迟惩罚).punpckhqdq
将高64位移动到低64位,但与更简单的命名方式movhlpd
不同,在整数模式下。32位移位在通用寄存器中完成。movhpd
在不干扰下半部分的情况下将一个双精度加载到xmm寄存器的上半部分-这用于加载a
这段代码明显比上面的代码快,而上面的代码又比简单的代码快,并且在每个访问模式上都是如此,但在简单的情况下,naive循环实际上是最快的。我还尝试了一些类似于SUM(a(B(:)、C(:)的函数
在Fortran中,它基本上等同于简单循环
我在一个Q6600(2.4Ghz的65纳米内核2)上测试了4个模块中的4GB DDR2-667内存。
测试内存带宽大约为5333MB/s,所以看起来我只看到了一个通道。我使用Debian的GCC4.3.2-1.1,-O3-Ffast math-msse2-Ftree vectorize-std=gnu99进行编译
对于测试,我让n
为一百万,初始化数组,使a[b[I]]
和c[I]
都等于1.0/(I+1)
,有一些不同的索引模式。一个分配a
100万个元素并将b
设置为随机排列,另一个分配a
10万个元素并每10万个使用一次,最后一个分配a
10万个元素并设置b[i+1]
通过将1到9之间的随机数添加到b[i]
。我用gettimeofday
计时一个调用所需的时间,通过在数组上调用clflush
清除缓存,并测量每个函数的1000次试验。我使用一些来自(特别是统计数据
包中的核密度估计器)
带宽
现在,关于带宽的实际重要注意事项。5333MB/s,2.4Ghz时钟,每个周期刚好超过两个字节。我的数据足够长,没有任何东西可以缓存,并将循环的运行时间乘以(16+2*16+4*64)每次迭代加载的字节数(如果所有内容都未命中)几乎正好为我提供了系统拥有的~5333MB/s带宽。如果没有SSE,应该很容易使带宽饱和。即使假设a
已完全缓存,只需读取b
和c
一次迭代即可移动12个字节的数据,天真的人也可以开始新的迭代使用流水线进行第三个周期的迭代
假设a
上没有完全缓存,那么算术和指令的瓶颈就更少了。如果我的代码中大部分的加速都来自于对b的加载更少,我也不会感到惊讶