软件学报
JOURN AL OF SOFTWARE
1999年　第10卷　第10期　Vol.10　No.10　1999



基于最佳并行度的任务依赖图调度 
杜建成 黄 皓 陈道蓄 谢 立

摘要　基于最佳并行度的任务依赖图调度策略充分利用编译时刻所得到的全局信息,采用横向和纵向任务合并,处理节点预分配,静态调度和动态调度相结合、集中式调度和分层调度相结合等措施,是一种简单的、具有较高效率的实用化调度方案.该调度方案能够在尽量压缩调度长度的情况下节约系统资源.
关键词　层次任务图,任务依赖图,静态调度,动态调度,最佳并行度.
中图法分类号　TP311
Optimum Degree of Parallelism-based Task Dependence 
Graph Scheduling Scheme
DU Jian-cheng HUANG Hao CHEN Dao-xu XIE Li
(State Key Laboratory for Novel Software Technology Nanjing University Nanjing 210093)
(Department of Computer Science and Technology Nanjing University Nanjing 210093)
Abstract Optimum degree of parallelism-based task dependence graph scheduling scheme fully utilizes the global information collected at compile-time, employs the techniques such as task merging in horizontal and vertical directions, processors pre-allocation, combination of static and dynamic scheduling, and integration of centralized scheduling and layer-scheduling. It is a simple, practical and effective scheduling method which addresses the problem of how to both reduce the execution time of programs and economize on processor resources.
Key words Hierarchical task graph, task dependence graph, static scheduling, dynamic scheduling, optimum degree of parallelism.

　　编写并行程序是一件相当复杂和困难的事情,自动并行编译的出现为充分有效地利用各种形式的并行体系结构提供了强大的支持.在进行串行程序的任务并行性分析时,首先要将整个程序划分成若干个模块或任务,然后分析这些任务的读写集,判定是否存在读写冲突,若无冲突,则二者可以并行执行.我们根据模块的自然边界进行任务的划分,这种划分往往可以提供较好的数据局部性.结果得到层次任务图HTG(hierarchical task graph).下一步的工作便是在并行环境中调度执行HTG中的任务.无论是在共享存储环境还是在分布存储环境,都常常采用动态调度和静态调度相结合的方法.静态调度是指在编译时刻所进行的调度,有时也称为预调度,可以利用编译时得到的全局信息进行调度优化.动态调度是指在程序执行过程中所进行的调度,具有较多的灵活性.图1是该系统的框架结构.其中:(1) 数据/控制依赖关系分析:分析程序中各个模块之间的依赖关系,分析结果是层次任务图HTG;(2) 任务粒度和通信代价预估:对各个任务的执行时间和任务间的通信量进行评估,并确定复合任务粒度的上限UL.

图1　系统框架
　　HTG的归一化:将任务粒度大于UL的复合节点的HTG图嵌入到初始HTG中,最终形成一个单一的任务依赖图TDG(task dependence graph).
　　TDG的分层化:用先广搜索的方法对TDG进行分层处理,使得同层任务之间没有依赖关系,可以并行执行,有依赖关系的节点分布在不同层中.结果形成分层任务图LTG(layout task graph).
　　任务的合并:由于最佳并行度和可用的处理节点的限制,并且为了实现任务粒度的均衡化,对同一层中的某些任务进行横向合并,同时,为了消除不必要的通信,在不同层中进行纵向合并.
　　任务的包装:在每个任务中添加必要的成分,使之成为可以独立编译和运行的单位.
　　处理节点预分配:让每个任务尽量分配在其前导节点所在的处理节点上,以便压缩通信开销.
　　分层调度:在程序运行时刻,接受前导任务发来的消息,将下一层的所有任务调度激活.
　　从图1中可以看出,我们采用了静态调度和动态调度相结合的方法,静态调度(预调度)部分包括TDG的分层化、任务的合并和处理节点预分配,分层调度属于动态调度.
1　相关工作
　　一般形式的优化调度问题都是NP难题[1],因此,实用的调度算法都是经验算法.文献[2]给出了一些算法,如HLF(high level first),CP(critical path),LPT(largest processing time),但是这些算法都基于共享存储模式,不考虑通信代价,不能很好地应用于分布存储系统.
　　文献[3]给出了一个可伸缩的调度算法SSS(scalable schedule scheme).SSS给出一个阈值,在调度长度和并行度之间取得折衷.其中的处理节点竞争算法仅考虑一个前导任务的若干个后继任务竞争处理节点的情况,而没有考虑一组前导任务的若干个后继任务相互竞争的情况.
　　文献[4]给出了共享存储模式下任务依赖图的调度.它将任务分为并行任务和串行任务两类,串
行任务比并行任务具有更高的优先级,以便于消除调度中的瓶颈,但在分布存储系统中,动态调度并行任务很困难.
　　本文力图解决这样一个问题:如何在尽量压缩调度长度的情况下节约系统资源.本文给出最佳并行度的概念,在调度及通信开销与程序并行性之间取得折衷,通过合并达到任务粒度的均衡化,通过后继任务间处理节点的分配算法来压缩通信开销.实验表明,当存在大量的并行小任务的情况下,本文的调度方案实现了上述目标.
2　预调度
2.1　几个约定
　　(1) TDG和LTG的约定
　　. TDG和LTG均是无环有向图(directed acirclic graph,简称DAG).
　　.前导任务与后继任务之间的先后或依赖关系用通信来表达,send原语为非阻塞通信原语(nonblocking),receive原语为阻塞通信原语(blocking).
　　.任务是非严格的,即后继任务可以在前导任务结束之前被调度,但若后继任务未完全获得所需要的依赖信息,将处于通信等待状态.
　　(2) 执行环境的约定
　　分布存储系统中的各个节点同构,任意两个节点之间通信速率相同.
　　(3) 任务模型
　　一个任务由以下4个部分组成,如图2所示.

图2　任务模型
　　.receive区:接受来自前导任务的消息.
　　. 计算区:任务的主体,完成本任务的所有计算.
　　.调度区:向调度器发出消息,指示调度器将后继任务调度激活.
　　.send区:向其后继任务发送消息.
　　并不是所有的任务都包含这4个区,例如TDG的首任务没有接受区,尾任务没有发送区,LTG中的每一层只需一个任务有调度区,因为调度器一次可以将下一层的所有任务激活.
　　(4) 调度模型
　　采用动态调度和静态调度相接合的策略,动态调度采用集中式方法,由一个调度器利用静态调度的结果,随时接受来自某一任务调度区的消息,对LTG实施调度.调度器每次调度LTG的一层中的所
有任务,直至尾任务执行完毕.
2.2　TDG的分层化
　　为了便于进行任务的合并和调度等后继工作,首先将TDG分层化,使同层中的任务之间没有依赖关系,进而可以并行执行,有依赖关系的节点分布在不同层中.节点p依赖于q,则称q为p的前导节点,p为q的后继节点.没有前导节点的节点为首节点,没有后继节点的节点为尾节点.算法1采用先广搜索的方法对TDG遍历,给每个节点一个标记,该标记指出节点的层号,这样就完成了TDG的分层化,结果形成分层任务图.
　　算法1. TDG的分层化
　　procedure LTDG
　　　Q=nil;
　　　l=0;
　　　for I=1 to n
　　　　　　　L(ni)=l;
　　　Q=Q∪{nfirst};
　　　L(nfirst)=l;
　　　while (Q<>nil)
　　　　　{
　　　　　　l=l+1;
　　　　　　q=a node from Q with indgree equal 0;
　　　　　　Q=Q- {q};
　　　　　　for (all nodes n adjacent to q)
　　　　　　　　　{
　　　　　　　　　L(n)=max(L(n),1);
　　　　　　　　　Q=Q∪{n};
　　　　　　　　　delete all arcs incident to this node;
　　　　　　　　　}
　　　　}
end
2.3　任务的合并
2.3.1 最佳并行度
　　考察如下结构的简单TDG,如图3所示.Ti(1in)依赖于T0,T0执行完之后,Ti可以并行执行.可以申请的处理节点数大于等于n+1,当T0执行到调度区时,向调度器发送消息,调度器接受到消息,将Ti(1in)全部调度激活,然后调度器向T0发送消息,报告Ti(1in)所在的位置,T0进入发送区,向Ti(1in)发送消息,Ti(1in)接受到消息便立即开始执行.T0,Ti(1in)和调度器执行时的时序关系如图4所示.
　　　　　　　　　　　　
　　　　图3　简单的TDG　　　　　　　　　　　　图4　任务执行时序图
　　T0与Ti(1in)之间的通信代价Ci=I+S*Di,I为通信启动代价,S为发送单位数据量的通信代价,Di为T0与Ti(1 i n)之间的通信量.调度一个任务的平均开销为SH,T0与Ti(1i n)的执行开销分别为t0与ti(1 i n).为简单起见,假定D,ti分别为常数D,t,Ci为常数C=I+S*D,T0与Ti(1i n)之间的通信开销始终存在,则Ti(1i n)的调度次序不对TDG的执行时间产生影响.在这种情况下,图3中TDG的执行时间为
　　　　　　　　　　 (1)
然后将T0的n个后继任务Ti(1in)均匀合并为m个任务T＇i(1im),mn,m|n,此时,每个新任务T＇i(1im)包含n/m个原任务,因此每个新任务的执行时间相同,均为t＇ =t*n/m,T＇i(1i m)与T0的通信代价C＇ =I+S*D*n/m,故合并后TDG的完成执行时间为
　　　　　　　　　　　　  　　　　 (2)
由式(1),(2)可得:当t/(SH+I)mn,且m|n时,
　　　　　　　　　　　　　CT＇  CT(3). 　　　　　　　　　　　　　　　　　　　　(3)
　　下面求使得CT＇最小的m值.为此,先对式(2)针对m求导:
(CT ＇)＇ =(SH+I)- t*n/m2
当(CT ＇)＇ =0,即(SH+I)- t*n/m2=0时,CT＇ 最小,此时.结合结论(3),我们给出如下定义:满足t/(SH+I) m n,m|n,且最接近的m为最佳并行度,记为pm,同时定义最佳粒度pt=n*t/pm=.以图3为例,若n=60,任务粒度相等,均为t=12,平均调度开销SH=2,任务起动时间I=1,t0=10,D=1,s=1,求得最佳并行度为15.事实上,通过表1也可以看出,当m t/(SH+I)=4时,CT＇CT,当m=15时,CT＇ 最小.
表1 并行度与TDG的执行时间

m60302015121054321
CT ＇262184166163166172229262319436793

　 
　　以上为了简单、方便起见,假定任务粒度和通信开销均相同.在实际应用中,情况不会是这样,但当并行任务数较多,且通信开销小于任务执行开销时,仍然可以近似地认为通信开销均相同,让t取任务的平均粒度,简单地定义最佳并行度pm=.总之,最佳并行度给出了调度、通信开销与并行性之间的一个折衷.
　　需要说明的是,最佳并行度指出了在任务可以并行执行情况下“最好的”并行任务个数,它不能决定任务并行执行是否一定比串行好.这也解释了pm的计算公式中没有出现任务通信量的原因.
2.3.2 任务的合并
　　由于特定执行环境中调度和通信开销的存在,使得任务粒度不能太小.而在原来的LTG中常常存在大量的小任务,因此有必要进行任务的合并.我们的任务合并包括两步,先在同层内进行横向合并,然后在不同层间进行纵向合并.
　　横向合并时,对LTG中的每一层,判定该层任务是否存在最佳并行度pm,若存在,则按pm进行任务的合并,如果可申请到的处理节点数ap小于pm,则按ap进行任务的合并.合并后的任务粒度应尽量趋于一致,使得该层中的任务并行执行时间最短.由于任务的均匀合并是一个NP问题,因此我们给出一个简单的经验化算法,如算法2所示.该算法对LTG进行操作,任意节点v的权为cw,表示v的执行开销,任意边e的权为(c,s,d),其中c表示通信内容,s为消息源,d为消息目的.
　　纵向合并时,对LTG中任一节点,若其出度为1,并且与之相联系的后继节点入度为1,则将这两个节点合并成一个节点.
　　这种合并方法具有简单、可伸缩性好的优点,并且不会破坏原有的LTG结构.
　　任务合并的一个极端情况是,整个LTG图被合并成一个节点,这代表了如下一种情况:虽然在串行程序中存在着并行性,但由于可并行任务粒度太小,使得在特定执行环境下并行执行没有意义,因此本系统指示其依然按照串行方式执行.
　　在进行任务横向合并时,需要知道每个任务的粒度.在编译时刻进行任务粒度分析已成为目前的研究热点[5].但是决定任务大小的一些参数并不都能在编译时刻确定,在这种情况下我们只能进行保守估计.保守估计的不精确性表现在将一些实际上的小任务估计为大任务.这在一定程度上影响了合并后的任务均衡性.我们采用的补救措施是:为LTG中某一层提供若干个可能的合并方案,当系统将要执行该层任务时,在大部分情况下关于该层的任务大小信息已经明朗了,系统可以较为容易地选择更加合适的合并结果去执行.
2.3.3 处理节点预分配
　　LTG在被执行时所需申请的处理节点的个数由LTG中“宽度”最大的层中的并行任务确定,设为pnum,可使用的处理节点的集合记为P.当任务vi与其某个前导任务vij分配在同一个处理节点pk(1 kpnum)上时,记为(p(vij),vi),p(vij)=pk,其通信开销将减少,因此在调度时应使每一个任务尽量分配在其前导任务所处节点之上,称为任务分配的亲近性.由于一个后继任务可能有若干个前导任务,一个前导任务可能有若干个后继任务,因此在给LTG中某一层的所有任务分配处理节点时,就存在选择与竞争两种情况.设合并后的分层任务图的第t层有k个任务v1,v2,...,vk,vi(1i k)位于t- 1层的前导任务集PS(vi)={vij|1jni},称为最近前导任务集.算法3用一种简单的方式解决t层中任务的处理节点预分配问题.对LTG中各层依次采用算法3进行任务的处理节点预分配.图5为LTG的一部分,a,b,c,d这4个节点的分配方案如表2所示.

图5　LTG的一部分
表2 处理节点预分配表

排序PS(d)={d＇ }PS(a)={a＇ ,b＇}PS(c)={a＇ ,b＇ ,c＇}PS(b)={b＇ ,c＇ ,d＇ }
(p(d＇),d)PS(a)={a＇ ,b＇}PS(c)={a＇ ,b＇ ,c＇}PS(b)={b＇ ,c＇ }
重排序PS(a)={a＇ ,b＇}PS(b)={b＇ ,c＇}PS(c)={a＇ ,b＇ ,c＇}
(p(b＇),a)PS(b)={c＇}PS(c)={a＇ ,c＇}
(p(c＇),b)PS(c)={a＇}
(p(a＇),c)

　 
3　分层调度
　　分层调度部分可以在编译后端,程序运行前进行,但我们将它推迟到运行时刻,这有以下几个原因:(1) 一开始将执行条件得不到满足的任务调度到各个处理节点上去,浪费大量的存储空间;(2) 在程序执行过程中,尽管这些任务被激活时,发现执行条件得不到满足会立即放弃对CPU的占用,但进程间的反复切换开销不容忽视,尤其在任务很多的情况下;(3) 由于很多信息都在预调度部分计算出来,因此分层调度本身的开销很小.
　　算法2. LTG中任务的合并
　　Procedure LTGmerge
　　{
　　　　for i=1 to m
　　　　　{
　　　　　　t=第i层所有任务执行开销之和ac/第i层任务数tn;
　　　　　　pm=;
　　　　　　rp=min(ap,pm);
　　　　　　at=ac/rp;
　　　　　　if (tn>rp) {
　　　　　　　　　　　　Q=第i层所有任务按执行开销c的降序排列;
　　　　　　　　　　　　Q＇ =Q的末尾的tn- rp个元素;
　　　　　　　　　　　　Q=Q- Q＇;
　　　　　　　　　　　　do {q=Q的首元素;
　　　　　　　　　　　　if (wqat) Q=Q- {q}
　　　　　　　　　　　　　　else break; }
　　　　　　　　　　while(Q<>nil);
　　　　　　　　　　while(Q＇ <>nil)
　　　　　　　　　　　　{q＇ =Q＇的首元素;
　　　　　　　　　　　　Q＇ =Q＇ - {q＇ }
　　　　　　　　　　　　lq=Q的尾元素;
　　　　　　　　　　　　lq=merge(lq,q＇ );
　　　　　　　　　　　　if (wlq<at)
　　　　　　　　　　　　　　将lq插入Q,使Q保持执行开销c的降序排列;}
　　　　　　　　　}
　　　　}
}
Function merge(v,v＇ )
{ 
　　for (每一个指向v＇ 的边e(c,s,v＇ ))
　　　　　{让e指向v;
　　　　　 修改e的权为e(c,s,v);}
　　for (由v￠ 发出的边e(c,v＇ ,d))
　　　　　{让e由v发出;
　　　　　 修改e的权为e(c,v,d);}
　　修改v的权为v(cv+cv＇ );
　　从LTG中删除v＇ ;
　　return(v);
}
　　我们的调度模型采用集中式模式,因为集中式调度器的设计较为简单,调度器每次调度LTG的一层中的所有任务,调度时刻由上一层中最先结束的任务决定(在2.1节中提到,我们的任务是非严格的,这一点允许后继任务在前导任务结束前得到调度.)无论是集中式调度还是分布式调度,都存在着瓶颈问题,但是可以通过仔细的设计来消除这种瓶颈.任务合并和分层调度的结合可以较好地消除这种瓶颈,其中任务合并实现了负载平衡,而分层调度大大压缩了LTG与调度器的通信次数,事实上,如果LTG有m层,则LTG只需向调度器发出m-1次调度请求.
　　算法3. 同层任务处理节点预分配
　　Procedure proallocate(t)
　　{
　　　Q=v1,v2,...,vk,按最近前导任务集数目增序的对列;
　　　　P=P- {t- 1层中使用的所有处理节点};
　　　　while(Q<>nil){
　　　　　　　v=Q的首元素;
　　　　　　　Q=Q- {v};
　　　　　　　If (PS(v)<>nil){
　　　　　　　　　　If (vj∈PS(v)&&v与vj之间的通信开销最大)
　　　　　　　　　　　(P(vj),v);}
　　　　else {p=P中的一个空闲处理节点;
　　　　　　　P=P- {p};
　　　　　　　　(p,v);}
　　　　for (Q的每个元素v＇ )
　　　　　　{if (vj∈PS(v＇ )
　　　　　　PS(v＇ )= PS(v＇ )- {vj};}
　　　　对Q中元素按最近前导任务集数目增序排队;
}
4　实例研究
　　为了测试以上调度算法的性能,需要较多的处理节点,由于条件所限,我们主要采用模拟的方法.我们在一台RS6000工作站上用21个进程模拟21个处理节点,每个进程具有相同的优先级,为了使它们能够分得相同个数的时间片,当一个进程“空闲”时,即未被调度器赋予计算任务时,也让其处于计算状态,借助PVM来完成节点之间的通信,通过这种方式来模拟一个同构的计算环境.在该环境中,调度一个任务的平均开销为30112m s,通信启动时间为239m s.我们设计了两组实验,第1组用来对最佳并行度进行测试,第2组对整个调度算法的性能进行评估.
　　实验1. 两个N*N的浮点矩阵AB差乘(float matrix multiply,简称FMM),并行执行时的任务图如图6所示.表3～6分别是60*60,80*80,100*100,120*120的矩阵差乘结果,时间以μs为单位,取10次执行结果的平均值,以下均同.

图6　FMM任务图
表3 60*60FMM

并行任务个数1(串行)235610
执行时间955 703597 312382 280227 547220 543455 095
加速比11.62.54.24.32.1
理论pm5
实际pm6

　 
表4 80*80 FMM

并行任务个数1(串行)246810
执行时间2 262 9601 432 227685 733435 176348 141452 584
加速比11.63.35.26.55.0
理论pm8
实际pm8

　 
表5 100*100 FMM

并行任务个数1(串行)2451020
执行时间4 428 9802 952 6541 342 1151 029 995560 630820 188
加速比11.53.34.37.95.4
理论pm12
实际pm10

　 
表6 120*120 FMM

并行任务个数1(串行)4610121520
执行时间7 229 9852 259 3701 390 380860 712777 418860 822881 705
加速比13.25.28.49.38.48.2
理论pm15
实际pm12

　 
　　从以上几组实验可以看出,理论pm和实际pm吻合得较好,说明最佳并行度能够反映并行性与调度及通信开销的折衷,这一点从图7可以清楚地看出来.

图7　并行任务数与加速比的关系
　　实验2. 为了对整个算法的性能进行评估,我们设计了塔形任务依赖图,如图8所示.图中每一层均比上一层多两个任务,相邻两层形成一个完全二分图,任务的粒度控制在5*104～21*104m s之间,任务间的通信量控制在1～5k byte之间,整个任务依赖图包括6层,共计36个任务.对该任务图的一种简单调度方法(SS)是:尽量利用可以得到的资源,即为每一个可以执行的任务分配一个处理节点,由于处理节点数21(一个节点被调度器占用)大于最大的任务宽度11,故该条件能够得到满足.我们针对该任务图做了20组实验,表7给出了3组实验结果,每组实验将基于最佳并行度的调度方法(optimum degree parallelism-based scheduling,简称OPBS)与上述的简单调度方法(SS)作了比较.表中可用p表示允许使用的处理节点数,实用p表示实际使用的处理节点数,均不包括调度节点.任务的串行执行时间等于该任务图中任务粒度之和.从表中可以看出,OPBS方法要比SS方法效率高,并且可以尽可能地节约处理器资源.附带说明一下,两种调度算法的加速比随处理节点个数的增多提高不大,主要是因为任务粒度不均匀,某一层任务的完成时刻常常由最大粒度任务的完成时刻所制约.而在实验1中,由于任务较为均匀,所以在一定范围内,加速比随处理节点个数的增多而提高很快.

图8　塔形任务图（前3层）
表7 塔形任务图的调度结果

实验组别串行执行并行执行
可用p实用p执行时间加速比
OPBSSSOPBSSSOPBSSS
148429364441 992 3252 466 7852.431.96
48429366661 971 0232 337 8022.452.17
48429368781 968 5992 308 8422.462.09
4842936117111 967 9432 084 3552.462.32
261245334442 662 8403 310 5582.301.85
61245336662 617 3212 958 7112.342.07
61245338782 171 8202 902 6222.722.11
6124533117112 164 5452 551 8892.832.39
354072654442 128 8442 457 8472.512.20
54072656662 032 8062 340 8072.662.31
54072658781 945 0572 207 0432.782.45
5407265117111 952 0812 216 0922.772.44

　 
5　结束语
　　本文提出的基于最佳并行度的任务依赖图调度算法,首先确定并行任务的最佳并行度,通过合并算法达到同层任务粒度的均衡化,通过处理节点的预分配压缩通信开销,实现在尽量压缩调度长度的情况下,尽量节约系统资源的目的.实验表明,在存在大量的并行小任务的情况下,本文的调度方案实现了预定目标.
注释：本文研究得到国家863高科技项目基金资助。
作者简介：杜建成：1971年生，博士，主要研究领域为并行编译
　　　　　黄皓：1957年生，副教授，主要研究领域为网络计算
　　　　　陈道蓄：1949年生，教授，主要研究领域为并行计算，分布式处理
　　　　　谢立：1942年，教授，博士生导师，主要研究领域为并行计算，分布式处理
作者单位:南京大学计算机软件新技术国家重点实验室 南京 210093
　　　　　南京大学计算机科学与技术系 南京 210093
参考文献
Graham R L, Lawler E L, Rinnooy Kan A H G. Optimization and approximation in deterministic sequencing and scheduling: a survey. In: Annals Discrete Mathematics. Amsterdam, Netherlands: North-Holland Publishing Company, 1979. 
287～326 
Wang Q, Cheng K H. List scheduling and parallel tasks. Information Processing Letters, 1991,37(5):78～87 
Pande S, Agrawal D R, Mauney J. A scalable scheduling scheme for functional parallelism on distributed memory multiprocessor systems. IEEE Transactions on Parallel and Distributed Systems, 1995,6(4):388～398 
Polychronopodlos C D. Parallel Programming and Compilers. Boston: Kluwer Academic Publishers Group, 1988. 83～111 
Liu Y A, Gomez G. Automatic accurate time-bound analysis for high-level languages. Technical Report, TR508, Indiana University, 1998 
收稿日期：1998-06-30修稿日期：1998-10-14
