これは長い投稿です。質問の前に多くの背景があります。簡単なバージョンは、リンクされたリストの要素で OpenMP を使用しようとしたことです。他の場所で規定されている方法で OpenMP タスクを使用しましたが、大幅な速度低下につながりました。ただし、物事を別の方法で分割すると、大幅なスピードアップを得ることができますが、最初の方法で作業する方法があるかどうか疑問に思っています。これは、よりクリーン/シンプルであり、(私が思うに) スレッド間で動的に作業のバランスを取るためです。
Fortran 型 (C 構造体) のかなり長いリンク リスト (数百万の要素になる可能性があります) があり、- 数回- リストを繰り返し処理し、各要素を操作する必要があります。したがって、サブルーチンを引数 (srt) として取り、リストの各要素に対してそれを操作するサブルーチン (eachPhonon) があります。
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 を使用すると非常に簡単になります。ただし、スタックオーバーフローとintelの両方で、リンクされたリストを使用してそれを行う方法を見てきました。基本的に、それは srt への各呼び出しを独自のタスクにします - 次のようなものです:
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
これは機能しているように見えますが、スレッドを 1 つだけ使用する場合よりも大幅に遅くなります。
たとえば、4 つのスレッドがある場合、1 つのスレッドが要素 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
これにより、大幅な高速化が可能になります。
最初の方法を効率的にする方法はありますか?
私は並列処理に慣れていませんが、最初のメソッドは要素ごとにタスクを作成しようとするため、オーバーヘッドが大きすぎると読んでいます。2 番目の方法では、スレッドごとに 1 つのタスクのみを作成し、そのオーバーヘッドを回避します。欠点は、openmp なしではコンパイルできないクリーンなコードではないことです。また、スレッド間で作業のバランスを動的にとることができません。すべての作業は最初に静的に割り当てられます。