我对代码优化技术并不熟悉,而且我目前正在尝试优化一段代码的循环部分,这应该很简单。
for (int i = 0; i < N; i++)
{
array[i] = 0.0f;
array2[i] = 0.0f;
array3[i] = 0.0f;
}
我尝试按如下方式实现矢量化和线程化:
int i;
int loop_unroll = (int) (N/4)*4;
#pragma omp parallel for shared(array,array2,array3)
for(int i = 0; i < loop_unroll; i+=4)
{
__m128 array_vector = _mm_load_ps(array+i);
array_vector = _mm_set1_ps(0.0f);
_mm_store_ps(array+i, array_vector);
_mm_store_ps(array2+i, array_vector);
_mm_store_ps(array3+i, array_vector);
}
for(;i<N;i++)
{
array[i] = 0.0f;
array2[i] = 0.0f;
array3[i] = 0.0f;
}
无论输入的大小是否与我一起运行,优化的&#39;版本总是需要更长时间 我认为这是由于与设置线程和寄存器相关的开销,但是对于程序变得太慢而无法使用之前的最大N,开销仍然没有被更快的代码所缓解。
这让我想知道所使用的优化技术是否被错误地实现了?
答案 0 :(得分:0)
Compiling + benchmarking with optimization disabled is your main problem。具有内在函数的未优化代码通常非常慢。 将内在函数与标量与禁用优化进行比较没有用,gcc -O0
通常会更多地伤害内在函数。
一旦您停止使用未经优化的代码浪费时间,您就会希望gcc -O3
将标量循环优化为3 memset
个操作,而不是在循环中交错3个存储流。
编译器(以及优化的libc memset
实现)擅长优化内存清零,并且可能比简单的手动矢量化做得更好。但是,它可能并不太糟糕,特别是如果你的阵列在L1d缓存中已经很热了。 (如果是这样的话,那么只使用128位存储比具有宽数据路径的CPU上的256位或512位向量要慢得多。)
我还不确定使用OpenMP进行多线程的最佳方法,同时仍然让编译器优化到memset
。让omp parallel for
并行化存储来自每个线程的3个流的代码可能并不可怕,只要每个线程在每个数组中处理连续的块。特别是如果以后更新相同数组的代码将以相同的方式分发,那么每个线程正在处理它之前归零的部分数组,并且在L1d或L2缓存中可能仍然很热。
当然,如果你可以避免它,那么动态归零作为另一个有一些有用计算的循环的一部分。如果您的下一步将是a[i] += something
,那么首先将其优化为a[i] = something
以便第一次通过数组,这样您就不需要先将其归零。
请参阅Enhanced REP MOVSB for memcpy了解大量x86内存性能详细信息,尤其是&#34;延迟限制平台&#34;这个部分解释了为什么单核线程内存带宽(到L3 / DRAM)在大型Xeon上比在四核桌面上更差,尽管当你有足够多的线程来自多个线程的聚合带宽可以更高线程使四通道存储器饱和。
对于商店性能(忽略缓存局部性以便以后的工作)我认为让每个线程工作(一大块)1个数组是最好的;单个顺序流可能是最大化单线程存储带宽的最佳方法。但是缓存是关联的,并且3个流足够少,以至于在写完整行之前它通常不会导致冲突失误。然而,桌面Skylake的L2缓存只是4路关联。
存储多个流有DRAM页面切换效果,每个线程1个流可能比每个线程3个流更好。因此,如果您确实希望每个线程将3个独立阵列的大块归零,理想情况下您希望每个线程将第一个阵列的整个块进行memset,然后是第二个阵列的整个块,而不是交错3个阵列。 / p>