这是一个很长的帖子 - 问题之前的很多背景。快速版本是我尝试在链接列表的元素上使用OpenMP - 以我在别处看到的方式使用OpenMP任务,但这会导致显着的减速。但是,如果我以不同的方式划分,我可以获得显着的加速,但我想知道是否有办法获得第一种工作方式,因为它更干净/更简单(我认为)它可以动态平衡线程中的工作。
我有一个相当长的链表(可能是几百万个元素)的Fortran类型(C结构)和 - 几次 - 我必须迭代列表并对每个元素进行操作。所以,我有一个子程序(eachPhonon),它将子程序作为参数(srt)并在列表的每个元素上进行操作:
subroutine eachPhonon(srt)
external :: srt
type(phonon), pointer :: tptr
tptr => head
do while(associated(tptr))
call srt(tptr)
tptr => tptr%next
enddo
endsubroutine
这似乎是并行加速的好地方,因为srt的每次调用都可以独立于其他调用完成。如果我有一个Fortran do(C for)循环,那么使用openmp会非常简单。但是,我已经在stackoverflow和intel上看到了如何使用链接列表进行操作的方法。基本上,它会使每次调用都成为自己的任务 - 类似于:
subroutine eachPhonon(srt)
external :: srt
type(phonon), pointer :: tptr
tptr => head
!$OMP PARALLEL
!$OMP SINGLE
do while(associated(tptr))
!$OMP TASK FIRSTPRIVATE(tptr)
call srt(tptr)
!$OMP END TASK
tptr => tptr%next
enddo
!$OMP END SINGLE
!$OMP END PARALLEL
endsubroutine
这似乎有效,但它比仅使用一个线程慢得多。
我重写了一些东西,所以给出了4个线程,一个线程可以运行元素1,5,9 ...,另一个运行元素2,6,10 ......等等:
subroutine everyNth(srt, tp, n)
external :: srt
type(phonon), pointer :: tp
integer :: n, j
do while(associated(tp))
call srt(tp)
do j=1,n
if(associated(tp)) tp => tp%next
enddo
enddo
endsubroutine
subroutine eachPhononParallel(srt)
use omp_lib
external :: srt
type(phonon), pointer :: tp
integer :: j, nthreads
!$OMP PARALLEL
!$OMP SINGLE
nthreads = OMP_GET_NUM_THREADS()
tp => head
do j=1,nthreads
!$OMP TASK FIRSTPRIVATE(tp)
call everyNth(srt, tp, nthreads)
!$OMP END TASK
tp => tp%next
enddo
!$OMP END SINGLE
!$OMP END PARALLEL
endsubroutine
这可以带来显着的加速。
有没有办法让第一种方法有效?
我是并行处理的新手,但我的阅读是第一种方法有太多的开销,因为它试图为每个元素创建一个任务。第二种方法只为每个线程创建一个任务,并避免这种开销。缺点是不那么干净的代码,如果没有openmp就无法编译,并且它不会动态平衡线程间的工作 - 它们在开始时都是静态分配的。
答案 0 :(得分:4)
如果您的并行度的粒度太小,您可能会尝试使用更大尺寸的块:
subroutine eachPhonon(srt,chunksize)
external :: srt
integer, intent(in) :: chunksize
type(phonon), pointer :: tptr
tptr => head
!$OMP PARALLEL
!$OMP SINGLE
do while(associated(tptr))
!$OMP TASK FIRSTPRIVATE(tptr)
! Applies srt(tptr) chunksize times or until
! associated(tptr)
call chunk_srt(tptr,chunksize)
!$OMP END TASK
! Advance tptr chunksize times if associated(tptr)
advance(tprt,chunksize)
enddo
!$OMP END SINGLE
!$OMP END PARALLEL
endsubroutine
我们的想法是将chunksize
设置为足以掩盖与任务创建相关的开销的值。
答案 1 :(得分:2)
减速意味着srt()
执行时间太短,因此开销会淹没可能的并行加速。除了Massimiliano的解决方案,你还可以将链表转换为指针数组,然后在结果结构上使用PARALLEL DO
:
type phononptr
type(phonon), pointer :: p
endtype phononptr
...
subroutine eachPhonon(srt)
external :: srt
type(phonon), pointer :: tptr
type(phononptr), dimension(:), allocatable :: ptrs
integer :: i
allocate(ptrs(numphonons))
tptr => head
i = 1
do while(associated(tptr))
ptrs(i)%p => tptr
i = i + 1
tptr => tptr%next
enddo
!$OMP PARALLEL DO SCHEDULE(STATIC)
do i = 1, numphonons
call srt(ptrs(i)%p)
enddo
!$OMP END PARALLEL DO
endsubroutine
如果没有明确地将列表项的数量保存在单独的变量中(在这种情况下为numphonons
),则必须遍历列表两次。 phononptr
类型是必需的,因为Fortran缺少一种更简单的方法来声明指针数组。
将Massimiliano的解决方案中的chunksize
设置为numphonons / omp_get_num_threads()
也可以实现同样的效果。