OpenMP: Scheduling

本文例子处代码块由于行宽不够,可能会对读者造成不便,可以将文本内容复制到notepad或者是visual studio code中查看,敬请谅解。

OpenMP的特长就在于并行循环。我们知道下面这段代码可以启用并行:

#pragma omp parallel for
for ( ... )
{ ... }

OpenMP对于并行化循环,会创建一个线程池,然后把for循环里的运算分配给线程。这里的运算是指一轮for循环(循环下标+1)。

Scheduling

接下来我们来讨论一下程序员如何决定一个循环的scheduling类型。

Explicit

如果有一个显式schedule子句:

#pragma omp parallel for schedule(scheduling-type)
for (...)
{ ... }

那么OpenMP就会使用scheduling-type来设定for loop的并行方式。

scheduling-type包括:

  • static
  • dynamic
  • guided
  • auto
  • runtime

我们会在后面详细讨论。

Runtime

如果scheduling-type(在宏定义里)是runtime,那么OpenMP将会调用系统设定的run-sched-var来决定schedule类型。设定OpenMP中的run-sched-var由两种方法:

  1. 我们可以通过设定环境变量OMP_SCHEDULE来设定run-sched-var

例如我们在bash-like的终端里输入

$ export OMP_SCHEDULE=scheduling-type

来设定环境变量OMP_SCHEDULE

  1. 另外我们也可以在自己程序里调用函数设定:
...
omp_set_schedule(scheduling-type);
...

Default

如果没有显式指定schedule子句

#pragma omp parallel for
for (...)
{ ... }

那么OpenMP使用Default scheduling模式。具体使用的类型将由OpemMP内部变量def-shed-var决定

The scheduling types

正如前面提到过的,一共有五种scheduing类型

scheduling-type包括:

  • static
  • dynamic
  • guided
  • auto
  • runtime

Static

schedule(static, chunk-size)子句指定了for循环使用static schedule模式。OpenMP把for的总共次数划分成一个个小块(chunks),chunk-size规定了每个线程一次执行多少次for循环。

如果没有指定chunk-size,OpenMP将会把for的迭代分成近乎相同的块,然后尽量给每个线程分配连续的一次运算。

举个例子:

schedule(static): --no chunk-size parameter
****************
                ****************
                                ****************
                                                ****************
schedule(static, 4):
****            ****            ****           ****
    ****            ****            ****            ****
        ****            ****            ****            ****
            ****            ****            ****            ****            
schedule(static, 8):
********                        ********
        ********                        ********
                ********                        ********
                        ********                        ********

对于这个例子,简单来说我们想要并行一个64次的循环,我们有4个线程。在上面的代码块里,每一行代表一个线程,每个*富豪代表一次循环。

在第一个代码块(对应schedule(static))里,第一个线程运行了16次循环,假设for循环里的循环变量为i,这16次循环对应了i=0,1,2,...15,同理,第二个线程运行了i=16,17,18,19...31

其他两个以此类推,每个线程执行的分别是四个连续的循环和8个连续的循环,类似i=0,1,2,3,8,9,10,11...

如果每个循环都有相同的计算时间的话,换言之,如果循环的计算和循环变量的增加没关系的话,使用static可以减小额外开销(overhead)。

Dynamic

schedule(dynamic, chunk-size)子句指定for loop有动态的scheduling type,OpenMP将会根据chunk-size划分运算块。每个块先运行OpenMP给分配好的一个chunk,运行完以后OpenMP将再次分配。直到没有剩余块

这意味着在运行程序前,我们无法预知线程和某次循环的对应关系,每次运行线程执行的顺序都会改变。

chunk-size默认为1.

例子:

schedule(dynamic):     
*   ** **  * * *  *      *  *    **   *  *  * *       *  *   *  
  *       *     *    * *     * *   *    *        * *   *    *   
 *       *    *     * *   *   *     *  *       *  *  *  *  *   *
   *  *     *    * *    *  *    *    *    ** *  *   *     *   * 
schedule(dynamic, 1):  
    *    *     *        *   *    * *  *  *         *  * *  * *  
*  *  *   * *     *  * * *    * *      *   ***  *   *         * 
 *   *  *  *  *    ** *    *      *  *  * *   *  *   *   *      
  *    *     *  **        *  * *    *          *  *    *  * *  *
schedule(dynamic, 4):  
            ****                    ****                    ****
****            ****    ****            ****        ****        
    ****            ****    ****            ****        ****    
        ****                    ****            ****            
schedule(dynamic, 8):  
                ********                                ********
                        ********        ********                
********                        ********        ********        
        ********                                                

可以发现前两种schedule是一致的。后面两种OpenMP分别给每个线程4或者8个大小的块,每个线程运行循环数量不一定相同。

动态scheduling type适用于在已知不同的iteration会有不同的时间消耗(cost)。交给OpenMP来处理负载可能比人手动会高效一些。但是动态分配的过程会显著提升overhead.

Guided

guideddynamic非常相似。对于guided, OpenMP依然是将多次循环分成一个个块,每个线程执行块,执行结束后再次向OpenMP索要新块,直到没有新块。

guided和dynamic的区别在于块的大小,dynamic里块的大小是固定的,guided的块的大小会调整为剩余迭代的次数和线程数之比。随着程序的运行,块的大小会越来越小。

我们可以使用chunk-size参数来规定guided最小块的大小。但是,最后一次迭代可能会小于chunk-size.

chunk-size默认为1

schedule(guided, 4):   
                                     *******                    
                ************                     ****    ****   
                            *********                           
****************                            *****    ****    ***
schedule(guided, 2):   
                ************                     ****     **    
                                     *******         ***    **  
                            *********                           
****************                            *****       **    **
schedule(guided):      
                            *********                        *  
                ************                     *******  ***   
                                     *******                   *
****************                            *****       **    * 
schedule(guided, 8):   
                ************                 ********        ***
****************                                                
                                     ********                   
                            *********                ********

这里一共有64次迭代,最开始,系统给每个线程分配16次迭代(16=64/4,64次迭代,4个线程),之后再分配是12(12=(64-16)/4)次,依次类推。

Guided适用于每次迭代时间消耗不同的情况。并且由于最开始块很大,所以会减少overhead。

Auto

这种情况下OpenMP自动给定scheduling类型。

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据