我正在尝试优化掩盖数组的算法。初始代码如下所示:
void mask(unsigned int size_x, unsigned int size_y, uint32_t *source, uint32_t *m)
{
unsigned int rep=size_x*size_y;
while (rep--)
{
*(source++) &= *(m++);
}
}
我试图做Loop Unrolling + prefetching
void mask_LU4(unsigned int size_x, unsigned int size_y, uint32_t *source, uint32_t *mask)
{ // in place
unsigned int rep;
rep= size_x* size_y;
rep/= 4 ;
while (rep--)
{
_mm_prefetch(&source[16], _MM_HINT_T0);
_mm_prefetch(&mask[16], _MM_HINT_T0);
source[0] &= mask[0];
source[1] &= mask[1];
source[2] &= mask[2];
source[3] &= mask[3];
source += 4;
mask += 4;
}
}
并使用内在函数
void fmask_SIMD(unsigned int size_x, unsigned int size_y, uint32_t *source, uint32_t *mask)
{ // in place
unsigned int rep;
__m128i *s,*m ;
s = (__m128i *) source;
m = (__m128i *) mask;
rep= size_x* size_y;
rep/= 4 ;
while (rep--)
{
*s = _mm_and_si128(*s,*m);
source+=4;mask+=4;
s = (__m128i *) source;
m = (__m128i *) mask;
}
}
但是性能是一样的。我试图对SIMD和循环展开版本执行sw预取,我看不到任何改进。关于如何优化此算法的任何想法?
P.S.1:我正在使用gcc 4.8.1,我用-march=native
和-Ofast
编译。
P.S.2:我使用的是英特尔酷睿i5 3470 @ 3.2Ghz,Ivy桥架构。 L1 DCache 4X32KB(8路),L2 4x256,L3 6MB,RAM-DDR3 4Gb(双通道,DRAM @ 798,1Mhz)
您的操作是内存带宽限制。但是,这并不一定意味着您的操作正在实现最大内存带宽。要接近最大内存带宽,您需要使用多个线程。使用OpenMP(将-fopenmp
添加到GCC的选项),您可以这样做:
#pragma omp parallel for
for(int i=0; i<rep; i++) { source[i] &= m[i]; }
如果您不想修改源但使用不同的目标,那么您可以使用如下的流指令:
#pragma omp parallel for
for(int i=0; i<rep/4; i++) {
__m128i m4 = _mm_load_si128((__m128i*)&m[4*i]);
__m128i s4 = _mm_load_si128((__m128i*)&source[4*i]);
s4 = _mm_and_si128(s4,m4);
_mm_stream_si128((__m128i*i)&dest[4*i], s4);
}
这不会比使用相同的目标和源更快。但是,如果您已经计划使用不等于源的目标,那么使用rep
可能会更快(对于_mm_store_si128
的某个值)。
您的问题可能是内存限制,但这并不意味着您无法在每个周期处理更多内容。通常当你有一个低负载操作(就像你在这里,它毕竟只是一个AND),组合许多负载和存储是有意义的。在大多数CPU中,大多数负载将由L2高速缓存组合成单个高速缓存线负载(特别是如果它们是连续的)。我建议在这里增加循环展开至少4个SIMD数据包,以及预取。你仍然受内存限制,但你会得到更少的缓存未命中,给你一个稍微好一点的性能。