OpenMP中,任务调度主要用于并行的for循环,当循环中每次迭代的计算量不相等时,如果简单地给各个线程分配相同次数的迭代的话,会造成各个线程计算负载不均衡,这会使得有些线程先执行完,有些后执行完,造成某些线程(核)空闲,影响程序性能。例如以下代码
DEFAULT schedule is to split the iterations in blocks across OpenMP threads
在这个例子中,schedule默认将并行域里的循环迭代分割,交给线程执行
如果将最外层循环并行,使用2个线程
绿色行 [即 row=1,2,3 ] 被thread 0 执行
紫色行 [即 row=4,5,6] 被thread 1 执行
如果给每个线程平均分配3次循环迭代计算的话,thread 0 进行了6次运算,thread 1 进行了15次运算。那么各个线程间可能出现较大的负载不平衡情况。为了解决这些问题,OpenMP中提供了几种对for循环并行化的任务调度方案。
在OpenMP中,对for循环并行化的任务调度使用schedule子句来实现,下面介绍schedule的用法:
schedule的使用格式为: schedule(type, chunk_size)
schedule有两个参数:type 和 size (size参数是可选择是否设置的)
表示调度类型,有四种调度类型如下:
dynamic
guided
runtime
static
这四种调度类型实际上只有static、dynamic、guided三种调度方式,runtime实际上是根据环境变量来选择前三种中的某中类型。 run-sched-var
size参数表示循环迭代次数,size参数必须是整数。static、dynamic、guided三种调度方式都可以使用size参数,也可以不使用size参数。当type参数类型为runtime时,size参数是非法的(不需要使用,如果使用的话编译器会报错)
当parallel for编译指导语句没有带schedule子句时,大部分系统中默认采用static调度方式
对于schedule的含义,OpenMP会给每个线程分配size次迭代计算。这个分配是静态的,“静态”体现在这个分配过程跟实际的运行是无关的,可以从逻辑上推断出哪几次迭代会在哪几个线程上运行
假设有n次循环迭代,t个线程,那么给每个线程静态分配大约n/t次迭代计算。
这里为什么说大约分配n/t次呢?因为n/t不一定是整数,因此实际分配的迭代次数可能存在差1的情况,如果指定了size参数的话,那么可能相差一个size。
静态调度时可以不使用size参数,也可以使用sizeze参数。
不使用size参数时,分配给每个线程的是n/t次连续的迭代,使用size参数的用法如下:
#pragma omp parallel for schedule(static)
静态调度(static) & 不使用size参数
四个线程,每个分配到2-3个循环,比如:
thread#0分配到的迭代: 0、1、2 ;
thread#1分配到的迭代: 3、4 ;
thread#2分配到的迭代:5、6、7;
thread#3分配到的迭代:8、9;
使用size参数时,分配给每个线程的size次连续的迭代计算,用法如下:
#pragma omp parallel for schedule(static, N)
静态调度(static) & 使用size参数
每个线程每次指定执行2个循环,所以线程0运行了4次,其他都是2次。
thread#0分配到的迭代: 0、1 、8、9;
thread#1分配到的迭代: 2、3;
thread#2分配到的迭代:4、5;
thread#3分配到的迭代:6、7;
动态调度是动态地将迭代分配到各个线程,分配是依赖于运行状态进行动态确定的,所以哪个线程上将会运行哪些迭代是无法像静态一样事先预料的
对于dynamic,没有size参数的情况下,每个线程按先执行完先分配的方式执行1次迭代。
动态调度可以使用size参数也可以不使用size参数
下面为使用动态调度不带size参数的例子:
#pragma omp parallel for schedule(dynamic)
动态调度(dynamic) & 不使用size参数
比如,刚开始,线程1先启动,那么会为线程1分配一次循环开始去执行(i=0的迭代),然后,可能线程2启动了,那么为线程2分配一次循环去执行(i=1的迭代),假设这时候线程0和线程3没有启动,而线程1的迭代已经执行完,可能会继续为线程1分配一次迭代,如果线程0或3先启动了,可能会为之分配一次迭代,直到把所有的迭代分配完
下面为使用动态调度带size参数的例子:
#pragma omp parallel for schedule(dynamic, N)
动态调度(dynamic) & 使用size参数
分配原理同无size参数,只是每次每个线程分配到3个迭代
注意:输出的结果的顺序不代表执行此迭代的顺序,但是逻辑迭代顺序是遵循以上逻辑
guided调度是一种采用指导性的启发式自调度方法。开始时每个线程会分配到较大的迭代块,之后分配到的迭代块会逐渐递减。迭代块的大小会按指数级下降到指定的size大小,如果没有指定size参数,那么迭代块大小最小会降到1。
下面为使用引导调度不带size参数的例子:
#pragma omp parallel for schedule(guided)
guided调度(guided)& 不使用size参数
分配数量逐渐从5,4,3,2减小。
下面为使用引导调度带size参数的例子:
#pragma omp parallel for schedule(guided,N)
guided调度(guided)& 使用size参数
分配数量逐渐从5减小到4
runtime调度并不是和前面三种调度方式似的真实调度方式,它是在运行时根据环境变量OMP_SCHEDULE来确定调度类型,最终使用的调度类型仍然是上述三种调度方式中的某种。
#pragma omp parallel for schedule(runtime)
runtime调度(rumtime)
在unix系统中,可以使用setenv命令来设置OMP_SCHEDULE环境变量:
setenv OMP_SCHEDULE “dynamic, 2”
上述命令设置调度类型为动态调度,动态调度的迭代次数为2。
在windows环境中,可以在”系统属性|高级|环境变量”对话框中进行设置环境变量
set OMP_SCHEDULE=“type, chunk_size”
举例: export OMP_SCHEDULE=“guided,10”