Performance 为什么阵列排列会影响加速?

Performance 为什么阵列排列会影响加速?,performance,fortran,openmp,gfortran,Performance,Fortran,Openmp,Gfortran,我正在使用gfortran和openmp为并行计算编写代码。在台式机(配备Intel(R)Core(TM)i3-3220 CPU@3.30GHz)上测试代码时,我注意到您排列数据的方式会显著影响运行时间和加速。实际上,在1D上使用nx单元排列阵列,或者在2D上使用相同数量的单元排列阵列,这些单元分布在nxx行和N_行上。请注意,N_threads表示使用的线程数 为了更好地理解和量化这一点,我编写了一个运行完全相同数量操作的简短代码。代码如下所示: program testMem Use omp

我正在使用gfortran和openmp为并行计算编写代码。在台式机(配备Intel(R)Core(TM)i3-3220 CPU@3.30GHz)上测试代码时,我注意到您排列数据的方式会显著影响运行时间和加速。实际上,在1D上使用nx单元排列阵列,或者在2D上使用相同数量的单元排列阵列,这些单元分布在nxx行和N_行上。请注意,N_threads表示使用的线程数

为了更好地理解和量化这一点,我编写了一个运行完全相同数量操作的简短代码。代码如下所示:

program testMem
Use omp_lib            
implicit real*8 (a-h,o-z)
integer nx,nxx,N_threads 
parameter (N_threads=4,nx=1E8,nxx=int(nx/N_threads))
real*8  x(1:nx)
real*8  xx(1:nxx,0:N_threads-1)

x(1:nx)=0.
xx(1:nxx,0:N_threads-1)=0.

call system_clock(count_rate=icr)
timerRate=real(icr)     

CALL OMP_SET_NUM_THREADS(N_threads)

! 1D
call system_clock(ic1)
t0=omp_get_wtime()      
!$omp parallel do shared (x) private(i,j)
do i=1,nx
  do j=1,100 
    x(i)=x(i)+real(j*j)
  end do
end do 
!$omp end parallel do
call system_clock(ic2)      
t1=omp_get_wtime()
write (*,*) (ic2-ic1)/timerRate,t1-t0

! 2D
call system_clock(ic1)
t0=omp_get_wtime()   
!$omp parallel do shared(x) private(i_threads,i,j)
do i_threads=0,N_threads-1
  do i=1,nxx
    do j=1,100 
      xx(i,i_threads)=xx(i,i_threads)+real(j*j)
    end do
  end do 
end do
!$omp end parallel do
call system_clock(ic2)      
t1=omp_get_wtime()
write (*,*) (ic2-ic1)/timerRate,t1-t0

end program
我的期望是,与阵列的排列无关,并行版本的代码运行速度比串行版本快。然而,我发现我的机器花了很多钱

                   1D           2D
serial            5.96         5.96     
1thread          21.30         5.98
2threads         10.72         2.98
3threads          8.20         8.11
4threads          6.30         2.91
我想知道是否有人能解释这里发生了什么?为什么在1D部分中,时间从~6s增加到~20s,从串行(不使用-fopenmp编译)增加到使用1个线程并行?为什么2D阵列的行为与观察到的一维阵列的行为如此不同

正如@IanBush所建议的,我使用隐式none并以真正简单的精度声明所有变量。现在运行代码需要花费大量时间

                   1D           2D
serial            4.61         4.13     
1thread          12.16         4.18
2threads          6.14         2.09
3threads          5.46         5.65
4threads          4.63         1.85
由于real simple precision的长度为32位,而real*8(双精度)的长度为64位,因此运行时间更好。然而,问题依然存在

我还澄清了@IanBush的建议,我使用安装在Ubuntu 12.04上的GNU Fortran(Ubuntu/Linaro 4.6.3-1ubuntu5)4.6.3。代码是用gfortran-mcmodel=medium-O3-fopenmp-ocmd编译的


提前感谢您的帮助

上述行为是由于错误共享问题造成的,该问题会对1D部件产生负面影响。这个问题在文献中是众所周知的,简言之,当多个线程在同一个缓存线上运行时就会出现。在1D部分,这种情况可能经常发生,因为两个线程可能会访问相邻的数组元素。相比之下,在上述代码的2D部分,每个线程都在自己的列上工作。因此,由于fortran是列顺序的,不同的线程处理的数据永远不会位于同一缓存线中

为了避免错误共享问题,建议使用“填充”,即强制线程访问不同的缓存线,这意味着线程访问不能位于同一缓存线中的数组元素。假设缓存线可以包含8个实*8,我修改了1D部分以消除错误共享问题,如下所示

! 1D
call system_clock(ic1)
t0=omp_get_wtime()      
!$omp parallel do shared (x) private(i,j,i_leap,ii)
do i=1,int(nx/nCacheSize)
  do i_leap=1,nCacheSize
    ii=(i-1)*nCacheSize+i_leap
    do j=1,100 
      x(ii)=x(ii)+real(j*j)
    end do
  end do
end do 
!$omp end parallel do
call system_clock(ic2)      
t1=omp_get_wtime()
write (*,*) (ic2-ic1)/timerRate,t1-t0  
                   1D           2D
serial            3.99         3.92     
1thread           3.86         3.93
2threads          2.05         2.07
3threads          2.19         5.46
4threads          1.98         1.98
请注意,nCacheSize声明为
参数(nCacheSize=8)

我在Intel(R)Core(TM)i5-3320M CPU@2.60GHz上测试了代码,结果如下

! 1D
call system_clock(ic1)
t0=omp_get_wtime()      
!$omp parallel do shared (x) private(i,j,i_leap,ii)
do i=1,int(nx/nCacheSize)
  do i_leap=1,nCacheSize
    ii=(i-1)*nCacheSize+i_leap
    do j=1,100 
      x(ii)=x(ii)+real(j*j)
    end do
  end do
end do 
!$omp end parallel do
call system_clock(ic2)      
t1=omp_get_wtime()
write (*,*) (ic2-ic1)/timerRate,t1-t0  
                   1D           2D
serial            3.99         3.92     
1thread           3.86         3.93
2threads          2.05         2.07
3threads          2.19         5.46
4threads          1.98         1.98
3线程运行时间的增加可能是由于cpu硬件由2个内核和每个内核2个线程组成


我强调指出,
nCacheSize
必须通过
参数(nCacheSize=8)
声明为常量,否则使用包含变量的边界计算
I_leap
并不能解决错误共享问题。

您能告诉我们什么编译器,什么版本的编译器,以及您正在使用的编译器和链接标志。请使用隐式无-我的观点是,回答那些不必费心做防御性编码基础的问题是不值得的。请不要使用完全非标准、不可移植的real*8。如果您的问题是openmp如何影响死代码消除,那当然取决于编译器和选项。如果不值得您花时间检查生成的代码,那么可能不值得我们检查。谢谢@IanBush的建议和关注。我在桌面计算机中使用的编译器是安装在Ubuntu 12.04上的gfortran。编译器的版本是GNU Fortran(Ubuntu/Linaro 4.6.3-1ubuntu5)4.6.3。代码是用gfortran-mcmodel=medium-O3-fopenmp-o编译的。据我所知,实数*8是双精度的,是一种标准的实数。隐式声明的使用不应影响计算持续时间。但是,我使用隐式none和默认real运行代码。行为是一样的。我编辑我的问题是为了提供所有这些信息。你不是在衡量你认为你是什么。第一个并行区域是OpenMP运行时创建线程池的地方,因此您在第一次度量中包含了重要的时间,这将解释大部分差异。在进行任何计时之前,添加一个单独的、空的、未计时的平行区域……感谢@JimCownie的建议。创建线程池所需的时间不应超过几毫秒。因此,它可能会对代码的行为产生轻微的影响,但不会达到所讨论的数量级。此外,我对1D和2D部分进行了排列测试,运行时是相同的。