线程的调度优化
1. 引言
通过前边的介绍,知道了并行区域,默认情况下会自动生成与CPU个数相等的线程,然后并行执行并行区域中的代码。对于并行区域中的for循环有特殊的声明方式,这样不同的线程可以分别运行for循环变量的不同部分。通过锁同步(atomic、critical、mutex函数)或事件同步(nowait、single、section、master)来实现并行区域的同步控制。
那么系统是如何对线程进行调度的呢?具体的调度策略均有底层完成,本节介绍几种for可以在上层对for循环进行控制的调度策略。
2. 调度策略
调度策略 功能 适用场合
static 循环变量区域分为n等份,每个线程平分n份任务 各个cpu的性能差别不大
dynamic 循环变量区域分为n等份,某个线程执行完1份之后执行其他需要执行的 cpu之间运行能力差异很大
那一份任务
guided 循环变量区域由大到小分为不等的n份,运行方法类似dynamic 由于任务比dynamic不同,
所以可以减少调度开销
runtime 在运行时来适用上述三种调度策略中的一种,默认使用static
示例:
3.1. static
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #include <iostream>
- #include <omp.h>
-
- int main()
- {
-
-
- #pragma omp parallel for schedule(static, 2)
- for(int i = 0; i < 10; ++i)
- {
-
-
- std::cout << "thread id: " << omp_get_thread_num() << " value: " << i << std::endl;
- }
-
- return 0;
- }
#include <iostream> #include <omp.h> int main() { //static调度策略,for循环每两次迭代分为一个任务 #pragma omp parallel for schedule(static, 2) for(int i = 0; i < 10; ++i) { //被分为5个任务,其中循环0~1,4~5, 8~9分配给了第一个线程, //其余的分配给第二个线程 std::cout << "thread id: " << omp_get_thread_num() << " value: " << i << std::endl; } return 0; }
3.2. dynamic
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #include <iostream>
- #include <omp.h>
-
- int main()
- {
-
- #pragma om parallel for schedule(dnamic, 2)
- for(int i = 0; i < 10; ++i)
- {
-
- std::cout << "thread id: " << omp_get_thread_num() << " value: " << i << std::endl;
- }
-
- return 0;
- }
#include <iostream> #include <omp.h> int main() { //dynamic调度策略,for循环每两次迭代分为一个任务 #pragma om parallel for schedule(dnamic, 2) for(int i = 0; i < 10; ++i) { //分为5个任务,只要有任务并且线程空闲,那么该线程会执行该任务 std::cout << "thread id: " << omp_get_thread_num() << " value: " << i << std::endl; } return 0; }
3.3. guided
guided调度策略与dynamic区别在于,所分的任务块是从大到小排列的。具体分块算法为:每块的任务大小为:【迭代次数/线程个数的二倍】。其中每个任务的最小迭代次数由guided声明设定,默认为1。
举例说明:
view plain
copy to clipboard
print
?
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- #pragma omp for schedule [guided, 80]
-
- for(int i = 0; i < 800; ++i)
- {
-
- }
#pragma omp for schedule [guided, 80] for(int i = 0; i < 800; ++i) { // ..... }
两个cpu,那么任务分配如下:
第一个任务: [800/(2*2)] = 200
第二个任务:第一个任务分了200,还有600,那么[600/(2*2)] = 150
第三个任务:第二个任务分了150,还有450,那么[450/2*2)] = 113
第四个人任务:第三个任务分了113,还有337,那么[337/(2*2)] = 85
第五个任务:第四个任务分了85,还有252,那么[252/(2*2)] = 63, 小于声明的80,那么这里为80
第六个任务:第五个任务分了80,还有172,根据声明,这里为80(因为会小于80)
第七个任务:第六个任务分了80,还有92,根据声明,这里为80(因为会小于80)
第八个任务:第七个任务分了80,还有12,根据声明,这里为12(因为不够80)
3.4.runtime
运行时底层动态选择调度策略。