我在执行具有并行执行的程序时遇到了一些麻烦。这是一个测试代码。
module test
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
contains
subroutine Addition(x,y,s)
real(dp),intent(in) :: x,y
real(dp), intent(out) :: s
s = x+y
end subroutine Addition
function linspace(length,xi,xf) result (vec)
! function to create an equally spaced vector given a begin and end point
real(dp),intent(in) :: xi,xf
integer, intent(in) :: length
real(dp),dimension(1:length) :: vec
integer ::i
real(dp) :: increment
increment = (xf-xi)/(real(length)-1)
vec(1) = xi
do i = 2,length
vec(i) = vec(i-1) + increment
end do
end function linspace
end module test
program paralleltest
use, intrinsic :: iso_fortran_env, only: dp => real64
use test
use :: omp_lib
implicit none
integer, parameter :: length = 1000
real(dp),dimension(length) :: x,y
real(dp) :: s
integer:: i,j
integer :: num_threads = 8
real(dp),dimension(length,length) :: SMatrix
x = linspace(length,.0d0,1.0d0)
y = linspace(length,2.0d0,3.0d0)
!$ call omp_set_num_threads(num_threads)
!$OMP PARALLEL DO
do i=1,size(x)
do j = 1,size(y)
call Addition(x(i),y(j),s)
SMatrix(i,j) = s
end do
end do
!$OMP END PARALLEL DO
open(unit=1,file ='Add6.dat')
do i= 1,size(x)
do j= 1,size(y)
write(1,*) x(i),";",y(j),";",SMatrix(i,j)
end do
end do
close(unit=1)
end program paralleltest
我通过以下方式运行程序gfortran-8 -fopenmp paralleltest.f03 -o pt.out -mcmodel=medium
然后export OMP_NUM_THREADS=8
这个简单的代码至少给我带来了两个关于并行做的大问题。首先,如果我以length = 1100
或更大的值运行,我会收到Segmentation fault (core dump)
错误消息,但使用较小的值,它可以毫无问题地运行。第二个是关于它需要的时间。当我用length = 1000
运行它(用time ./pt.out
运行)运行时,它所需的时间是1,732s
但是如果我以顺序方式运行它(不调用-fopenmp
库并使用taskset -c 4 time./pt.out
)它需要1,714s
。我想这两种方式之间的差异出现在更长和更复杂的代码中,其中并行更有用。 事实上,当我尝试使用与八个线程并行运行的更复杂的计算时,时间减少了一半,而不是我预期的八分之一。鉴于此,我的问题是,是否有任何优化始终可用或是否依赖于代码?其次,有没有一种友好的方法来控制哪个线程运行哪个迭代?这是第一次运行第一个length/8
迭代,依此类推,例如使用不同的代码执行多个taskset
,其中每个代码都是我想要的迭代。
正如我评论的那样,分段错误已在其他地方处理 为什么在此 openmp 代码中发生分段错误?,我会使用可分配数组,但您也可以使用ulimit -s
设置堆栈大小。
关于时间,几乎所有的运行时都花在将数组写入外部文件上。
但是,即使您删除它并使用omp_get_wtime()
测量仅在并行部分中花费的时间并增加问题大小,它仍然不能很好地扩展。这是因为 CPU 要做的计算很少,并且大量数组写入内存(访问主内存很慢 - 缓存未命中)。
正如Jean-Claude Arbaut指出的那样,您的循环顺序是错误的,并且使访问内存的速度更加慢。一些编译器可以通过更高的优化级别(-O2
或-O3
)为您更改它,但仅限于其中的一部分。
更糟糕的是,正如Jim Cownie指出的那样,你有一个竞争条件。多个线程尝试使用相同的s
进行读取和写入,并且程序无效。您需要使用private(s)
使s
私有。
通过上述修复,我获得了大约快两倍的并行部分,具有四个内核和四个线程。不要尝试使用超线程,这会减慢程序速度。
如果你给CPU更多的计算工作去做,比如s = Bessel_J0(x)/Bessel_J1(y)
它对我来说扩展得很好,四个线程的速度几乎快了四倍,超线程确实会加快一点。
最后,我建议只是删除线程数的手动设置,这对于测试来说很痛苦。如果删除它,则可以轻松使用OMP_NUM_THREADS=4 ./a.out
。