OpenMP 中的线程任务调度

简介: OpenMP中任务调度主要针对并行的for循环,当循环中每次迭代的计算量不相等时,如果简单地给各个线程分配相同次数的迭代,则可能会造成各个线程计算负载的不平衡,影响程序的整体性能。 如下面的代码中,如果每个线程执行的任务数量平均分配,有的线程会结束早,有的线程结束晚: 1 #include...

OpenMP中任务调度主要针对并行的for循环,当循环中每次迭代的计算量不相等时,如果简单地给各个线程分配相同次数的迭代,则可能会造成各个线程计算负载的不平衡,影响程序的整体性能。

如下面的代码中,如果每个线程执行的任务数量平均分配,有的线程会结束早,有的线程结束晚:

 1 #include<stdio.h>
 2 #include<omp.h>
 3 
 4 int main(){
 5     int a[100][100] = {0};
 6 #pragma omp parallel for
 7     for (int i =0; i < 100; i++){
 8         for(int j = i; j < 100; j++ )
 9             a[i][j] = ((i%7)*(j%13)%23);
10     }
11     return 0;
12 }

 

为此,OpenMP提供了schedule子句来实现任务的调度。

schedule子句:

  schedule(type[, size]),

  参数type是指调度的类型,可以取值为static,dynamic,guided,runtime四种值。其中runtime允许在运行时确定调度类型,因此实际调度策略只有前面三种。

  参数size表示每次调度的迭代数量,必须是整数。该参数是可选的。当type的值是runtime时,不能够使用该参数。

1.静态调度static

  大部分编译器在没有使用schedule子句的时候,默认是static调度。static在编译的时候就已经确定了,那些循环由哪些线程执行。

  当不使用size 时,将给每个线程分配┌N/t┐个迭代。当使用size时,将每次给线程分配size次迭代。

  如下面代码:

 1 #include<stdio.h>
 2 #include<omp.h>
 3 int main(){
 4     int a[100][100] = {0};
 5 #pragma omp parallel for schedule(static)
 6 //#pragma omp parallel for schedule(static,5)
 7     for (int i =0; i < 100; i++){
 8         printf("id=%d i=%d\n",omp_get_thread_num(),i);
 9     }
10     return 0;
11 }
View Code

  在四核机器上执行:

  (1)当不使用参数时,100/4=25,0-24由1号线程执行;25-49由2号线程执行;50-74由3号线程执行;75-99由4号线程执行

  (1)当不使用参数时,x(x=0,1,2,3)线程执行((n/5)%4)任务。其中n=0-99。

2.动态调度dynamic

  动态调度依赖于运行时的状态动态确定线程所执行的迭代,也就是线程执行完已经分配的任务后,会去领取还有的任务。由于线程启动和执行完的时间不确定,所以迭代被分配到哪个线程是无法事先知道的。

  当不使用size 时,是将迭代逐个地分配到各个线程。当使用size 时,逐个分配size个迭代给各个线程。

  如下面代码:

 1 #include<stdio.h>
 2 #include<omp.h>
 3 int main(){
 4     int a[100][100] = {0};
 5 #pragma omp parallel for schedule(dynamic)
 6 //#pragma omp parallel for schedule(dynamic,5)
 7     for (int i =0; i < 100; i++){
 8         printf("id=%d i=%d\n",omp_get_thread_num(),i);
 9     }
10     return 0;
11 }
View Code

3.启发式调度guided

   采用启发式调度方法进行调度,每次分配给线程迭代次数不同,开始比较大,以后逐渐减小。

  size表示每次分配的迭代次数的最小值,由于每次分配的迭代次数会逐渐减少,少到size时,将不再减少。如果不知道size的大小,那么默认size为1,即一直减少到1。具体采用哪一种启发式算法,需要参考具体的编译器和相关手册的信息。

 

三种运行方式总结:

静态调度static:每次哪些循环由那个线程执行时固定的,编译调试。由于每个线程的任务是固定的,但是可能有的循环任务执行快,有的慢,不能达到最优。

动态调度dynamic:根据线程的执行快慢,已经完成任务的线程会自动请求新的任务或者任务块,每次领取的任务块是固定的。

启发式调度guided:每个任务分配的任务是先大后小,指数下降。当有大量任务需要循环时,刚开始为线程分配大量任务,最后任务不多时,给每个线程少量任务,可以达到线程任务均衡。

相关文章
|
3月前
|
调度
多进程并行执行
多进程并行执行
34 0
|
2天前
|
缓存 监控 Java
Java并发编程:线程池与任务调度
【4月更文挑战第16天】Java并发编程中,线程池和任务调度是核心概念,能提升系统性能和响应速度。线程池通过重用线程减少创建销毁开销,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。任务调度允许立即或延迟执行任务,具有灵活性。最佳实践包括合理配置线程池大小、避免过度使用线程、及时关闭线程池和处理异常。掌握这些能有效管理并发任务,避免性能瓶颈。
|
3月前
|
存储 监控 Java
GolangGMP模型 GMP(三):协程让出,抢占,监控与调度
GolangGMP模型 GMP(三):协程让出,抢占,监控与调度
25 0
|
8月前
|
算法 Java Go
【多线程系列-03】深入理解java中线程的生命周期,任务调度
【多线程系列-03】深入理解java中线程的生命周期,任务调度
128 0
|
8月前
|
存储 Go 调度
面试题:对于GoRoutine的调度理解(GMP)
对于GoRoutine的调度,使用了一种称为GMP的调度模型
113 0
|
9月前
|
Java 调度
任务调度线程池
任务调度线程池
|
11月前
|
存储 消息中间件 安全
Python基于线程的并行和基于进程并行详解
当涉及到并行编程时,Python标准库提供了两种不同的方式:基于线程的并行(threading)和基于进程的并行(multiprocessing)。下面我将从概念、性能、使用场景和底层实现等方面对它们进行解释和比较。
172 0
|
11月前
|
算法 编译器 API
|
11月前
|
API 调度 索引
|
Linux 调度 Android开发
RK3399平台开发系列讲解(进程调度篇)14.8、CPU 上下文切换
RK3399平台开发系列讲解(进程调度篇)14.8、CPU 上下文切换
73 0
RK3399平台开发系列讲解(进程调度篇)14.8、CPU 上下文切换