使用带外部循环的 OMP TASK 和带内部循环的 OMP TASKLOOP 有什么区别?
What is the difference between using OMP TASK with a loop outside and OMP TASKLOOP with a loop inside?
我正在尝试使用 OpenMP
(并行)执行一些存储在名为 tasklist_GRAD
的数组中的任务。
为了做到这一点,这是我实现的代码:
子例程 master_worker_execution(self,var,tasklist_GRAD,first_task,last_task)
type(tcb),dimension(20),intent(inout)::tasklist_GRAD !< the master array of tasks
integer::i_task !< the task counter
type(tcb)::self !< self
integer,intent(in)::first_task,last_task
type(variables),intent(inout)::var !< the variables
!OpenMP variables
integer::num_thread !< the rank of the thread
integer::nthreads !< the number of threads
integer:: OMP_GET_THREAD_NUM !< function to get the rank of the thread
integer::OMP_GET_NUM_THREADS !< function to get the number of threads
!=======================================================================================================================================================
!$OMP PARALLEL PRIVATE(num_thread,nthreads,i_task) &
!$OMP SHARED(tasklist_GRAD,self,var)
num_thread=OMP_GET_THREAD_NUM() !< le rang du thread
nthreads=OMP_GET_NUM_THREADS() !< le nombre de threads
!$OMP SINGLE
do i_task=first_task,last_task
tasklist_GRAD(i_task)%state=STATE_RUNNING
end do
!$OMP TASK UNTIED
do i_task=first_task,last_task
!$OMP TASK FIRSTPRIVATE(i_task) SHARED(tasklist_GRAD,self,var)
call tasklist_GRAD(i_task)%f_ptr(self,var)
!$OMP END TASK
!$OMP TASKWAIT !< comment this to compare between the first and the second code
end do
!$OMP END TASK
do i_task=first_task,last_task
tasklist_GRAD(i_task)%state=STATE_INACTIVE
end do
!$OMP END SINGLE
!$OMP END PARALLEL
end subroutine master_worker_execution
end module master_worker
在执行第一个代码时,我发现 !$OMP TASKLOOP
然后编写了以下代码:
!=======================================================================================================================================================
!$OMP PARALLEL PRIVATE(num_thread,nthreads,i_task) &
!$OMP SHARED(tasklist_GRAD,self,var)
num_thread=OMP_GET_THREAD_NUM() !< le rang du thread
nthreads=OMP_GET_NUM_THREADS() !< le nombre de threads
!$OMP SINGLE
!$OMP TASKLOOP PRIVATE(i_task) SHARED(tasklist_GRAD,self,var) NUM_TASKS(last_task-first_task+1)
do i_task=first_task,last_task
call tasklist_GRAD(i_task)%f_ptr(self,var)
end do
!$OMP END TASKLOOP
!$OMP END SINGLE
!$OMP END PARALLEL
我有3个问题(如果你认为我不应该问第二个和第三个那么我可以post 2个新问题)。
第一个和第二个代码有什么区别?对我来说,这是一回事(我认为在这里添加 grainsize(1)
是没有用的,因为我精确了任务的数量)。
如果我在第一个代码中退出 !$OMP TASKWAIT
会怎样?使用和不使用 TASKWAIT
结构的代码有什么区别?
是否需要大量的任务才能正确使用任务构造?
这两种方法的主要区别在于任务同步点在哪里以及同步哪些任务。当您的程序遇到 任务构造 时,会生成 任务 ,但不一定立即执行(在任务术语中,它的执行被推迟)。任务只能保证在程序退出时和以下三种结构之一完成:
- barrier(隐式或显式)。例如。在
single
构造的末尾和 parallel
区域的末尾有一个隐含的屏障。在屏障中,团队生成的所有显式任务都必须执行到完成。 (请注意,为了编写高效的代码,有必要知道隐含的障碍在哪里,因此如果程序的逻辑允许,您可以使用 nowait
子句跳过它们。)
- taskwait 构造指定 等待当前任务(不包括其后代)的子任务 完成。
- taskgroup: 在
taskgroup
区域的末尾指定一个等待taskgroup集合中创建的子任务及其后代的完成。这保证所有后代任务也已完成。
如您所见,如果存在后代任务或存在在 taskgroup
构造之前创建的任务,taskwait
和 taskgroup
在这方面可能会完全不同。
OpenMP 4.5 中添加了 taskloop
结构,结合了
使用具有任务分配灵活性的并行循环。在规范中,您可以阅读以下有关 taskloop
的内容:
By default, the taskloop construct executes as if it was enclosed in a
taskgroup construct with no statements or directives outside of the
taskloop construct. Thus, the taskloop construct creates an implicit
taskgroup region. If the nogroup clause is present, no implicit
taskgroup region is created.
表示你的第二个密码:
!$OMP TASKLOOP PRIVATE(i_task) SHARED(tasklist_GRAD,self,var) NUM_TASKS(last_task-first_task+1)
do i_task=first_task,last_task
call tasklist_GRAD(i_task)%f_ptr(self,var)
end do
!$OMP END TASKLOOP
在 taskloop
的末尾有一个隐含的 !$OMP END TASKGROUP
子句(即等待当前任务及其后代任务的子任务完成。)
另一方面,您的第一个代码
do i_task=first_task,last_task
!$OMP TASK FIRSTPRIVATE(i_task) SHARED(tasklist_GRAD,self,var)
call tasklist_GRAD(i_task)%f_ptr(self,var)
!$OMP END TASK
!$OMP TASKWAIT !< comment this to compare between the first and the second code
end do
完全不同,因为创建任务时 !$OMP TASKWAIT
将等待其完成,但不会等待任何后代任务。仅当子任务完成时才安排下一个任务。这实际上意味着如果没有后代任务(即 call tasklist_GRAD(i_task)%f_ptr(self,var)
中没有创建其他任务),您的程序将连续运行而不是同时运行。所以,!$OMP TASKWAIT
应该放在 end do
:
之后
do i_task=first_task,last_task
!$OMP TASK FIRSTPRIVATE(i_task) SHARED(tasklist_GRAD,self,var)
call tasklist_GRAD(i_task)%f_ptr(self,var)
!$OMP END TASK
end do
!$OMP TASKWAIT !< comment this to compare between the first and the second code
在这种情况下,首先创建所有任务,然后 !$OMP TASKWAIT
等待它们完成。如果没有后代任务,它与使用 taskloop
.
的第二个代码执行相同的操作
注意!$OMP TASK UNTIED
和对应的!$OMP END TASK
应该在你的第一个代码中删除,没有必要...
回答你的第二个问题,如果你在第一个代码中删除!$OMP TASKWAIT
,任务同步点将是!$OMP END SINGLE
,所以在任务完成之前可能会遇到tasklist_GRAD(i_task)%state=STATE_INACTIVE
。我猜这不是你的本意。
回答您的第三个问题:无论创建的任务数量如何,Tasking 都能正常工作。唯一的问题是效率,如果任务太少会导致负载不平衡,如果任务太多会导致开销,但它是 system/implementation 依赖的。
我正在尝试使用 OpenMP
(并行)执行一些存储在名为 tasklist_GRAD
的数组中的任务。
为了做到这一点,这是我实现的代码:
子例程 master_worker_execution(self,var,tasklist_GRAD,first_task,last_task)
type(tcb),dimension(20),intent(inout)::tasklist_GRAD !< the master array of tasks
integer::i_task !< the task counter
type(tcb)::self !< self
integer,intent(in)::first_task,last_task
type(variables),intent(inout)::var !< the variables
!OpenMP variables
integer::num_thread !< the rank of the thread
integer::nthreads !< the number of threads
integer:: OMP_GET_THREAD_NUM !< function to get the rank of the thread
integer::OMP_GET_NUM_THREADS !< function to get the number of threads
!=======================================================================================================================================================
!$OMP PARALLEL PRIVATE(num_thread,nthreads,i_task) &
!$OMP SHARED(tasklist_GRAD,self,var)
num_thread=OMP_GET_THREAD_NUM() !< le rang du thread
nthreads=OMP_GET_NUM_THREADS() !< le nombre de threads
!$OMP SINGLE
do i_task=first_task,last_task
tasklist_GRAD(i_task)%state=STATE_RUNNING
end do
!$OMP TASK UNTIED
do i_task=first_task,last_task
!$OMP TASK FIRSTPRIVATE(i_task) SHARED(tasklist_GRAD,self,var)
call tasklist_GRAD(i_task)%f_ptr(self,var)
!$OMP END TASK
!$OMP TASKWAIT !< comment this to compare between the first and the second code
end do
!$OMP END TASK
do i_task=first_task,last_task
tasklist_GRAD(i_task)%state=STATE_INACTIVE
end do
!$OMP END SINGLE
!$OMP END PARALLEL
end subroutine master_worker_execution
end module master_worker
在执行第一个代码时,我发现 !$OMP TASKLOOP
然后编写了以下代码:
!=======================================================================================================================================================
!$OMP PARALLEL PRIVATE(num_thread,nthreads,i_task) &
!$OMP SHARED(tasklist_GRAD,self,var)
num_thread=OMP_GET_THREAD_NUM() !< le rang du thread
nthreads=OMP_GET_NUM_THREADS() !< le nombre de threads
!$OMP SINGLE
!$OMP TASKLOOP PRIVATE(i_task) SHARED(tasklist_GRAD,self,var) NUM_TASKS(last_task-first_task+1)
do i_task=first_task,last_task
call tasklist_GRAD(i_task)%f_ptr(self,var)
end do
!$OMP END TASKLOOP
!$OMP END SINGLE
!$OMP END PARALLEL
我有3个问题(如果你认为我不应该问第二个和第三个那么我可以post 2个新问题)。
第一个和第二个代码有什么区别?对我来说,这是一回事(我认为在这里添加
grainsize(1)
是没有用的,因为我精确了任务的数量)。如果我在第一个代码中退出
!$OMP TASKWAIT
会怎样?使用和不使用TASKWAIT
结构的代码有什么区别?是否需要大量的任务才能正确使用任务构造?
这两种方法的主要区别在于任务同步点在哪里以及同步哪些任务。当您的程序遇到 任务构造 时,会生成 任务 ,但不一定立即执行(在任务术语中,它的执行被推迟)。任务只能保证在程序退出时和以下三种结构之一完成:
- barrier(隐式或显式)。例如。在
single
构造的末尾和parallel
区域的末尾有一个隐含的屏障。在屏障中,团队生成的所有显式任务都必须执行到完成。 (请注意,为了编写高效的代码,有必要知道隐含的障碍在哪里,因此如果程序的逻辑允许,您可以使用nowait
子句跳过它们。) - taskwait 构造指定 等待当前任务(不包括其后代)的子任务 完成。
- taskgroup: 在
taskgroup
区域的末尾指定一个等待taskgroup集合中创建的子任务及其后代的完成。这保证所有后代任务也已完成。
如您所见,如果存在后代任务或存在在 taskgroup
构造之前创建的任务,taskwait
和 taskgroup
在这方面可能会完全不同。
OpenMP 4.5 中添加了 taskloop
结构,结合了
使用具有任务分配灵活性的并行循环。在规范中,您可以阅读以下有关 taskloop
的内容:
By default, the taskloop construct executes as if it was enclosed in a taskgroup construct with no statements or directives outside of the taskloop construct. Thus, the taskloop construct creates an implicit taskgroup region. If the nogroup clause is present, no implicit taskgroup region is created.
表示你的第二个密码:
!$OMP TASKLOOP PRIVATE(i_task) SHARED(tasklist_GRAD,self,var) NUM_TASKS(last_task-first_task+1)
do i_task=first_task,last_task
call tasklist_GRAD(i_task)%f_ptr(self,var)
end do
!$OMP END TASKLOOP
在 taskloop
的末尾有一个隐含的 !$OMP END TASKGROUP
子句(即等待当前任务及其后代任务的子任务完成。)
另一方面,您的第一个代码
do i_task=first_task,last_task
!$OMP TASK FIRSTPRIVATE(i_task) SHARED(tasklist_GRAD,self,var)
call tasklist_GRAD(i_task)%f_ptr(self,var)
!$OMP END TASK
!$OMP TASKWAIT !< comment this to compare between the first and the second code
end do
完全不同,因为创建任务时 !$OMP TASKWAIT
将等待其完成,但不会等待任何后代任务。仅当子任务完成时才安排下一个任务。这实际上意味着如果没有后代任务(即 call tasklist_GRAD(i_task)%f_ptr(self,var)
中没有创建其他任务),您的程序将连续运行而不是同时运行。所以,!$OMP TASKWAIT
应该放在 end do
:
do i_task=first_task,last_task
!$OMP TASK FIRSTPRIVATE(i_task) SHARED(tasklist_GRAD,self,var)
call tasklist_GRAD(i_task)%f_ptr(self,var)
!$OMP END TASK
end do
!$OMP TASKWAIT !< comment this to compare between the first and the second code
在这种情况下,首先创建所有任务,然后 !$OMP TASKWAIT
等待它们完成。如果没有后代任务,它与使用 taskloop
.
注意!$OMP TASK UNTIED
和对应的!$OMP END TASK
应该在你的第一个代码中删除,没有必要...
回答你的第二个问题,如果你在第一个代码中删除!$OMP TASKWAIT
,任务同步点将是!$OMP END SINGLE
,所以在任务完成之前可能会遇到tasklist_GRAD(i_task)%state=STATE_INACTIVE
。我猜这不是你的本意。
回答您的第三个问题:无论创建的任务数量如何,Tasking 都能正常工作。唯一的问题是效率,如果任务太少会导致负载不平衡,如果任务太多会导致开销,但它是 system/implementation 依赖的。