一种基于任务窃取的任务调度方法及系统与流程

文档序号:11199055阅读:580来源:国知局
一种基于任务窃取的任务调度方法及系统与流程

本发明属于计算机并行程序设计技术领域,更具体地,涉及一种基于任务窃取的任务调度方法及系统。



背景技术:

不断逼近物理极限的晶体管尺寸以及功耗严重限制了计算机中单核处理器的发展,再也无法像以前一样只用等待芯片制造商推出新的处理器就能获得程序性能的提升。为进一步提升应用程序性能,只有依赖将多个核心集成到单个cpu中并将应用程序并行化的方法。单核串行时代已经结束,程序员开始迈向多核并行时代。

传统的并行编程模型(包括mpi和较早版本的openmp)只面向专家级、资深程序员或者只能适应规则的应用。多核时代需要的是面向更广阔应用领域的、易编程、高产能的并行编程工具。近几年涌现出许多新型并行编程模型,其中,任务级并行编程模型因为具有适用面广、编程方便、计算资源使用率高的优点而成为多核平台上首选的并行编程模型。任务级并行编程模型把任务作为并行的基本单位,提供任务划分和同步的编程接口,把任务划分和同步工作交给程序员完成,用户可以把应用程序划分出大量细粒度任务。然而,具体到每个任务到底是并行执行还是串行执行、在哪个物理核上执行以及如何实现任务之间的同步则由运行时系统完成。任务级并行编程模型提倡嵌套的递归任务,并引入以任务窃取算法为核心的用户级线程调度,实现程序的高性能和动态的负载平衡。

与一般程序类似,任务级并行编程模型中允许程序员使用控制流以实现程序逻辑。在控制流中的基本块末尾,程序员可自行添加或由运行时隐式添加同步操作以在基本块末尾处等待基本块中所有任务执行完毕以防止基本块之间在执行时出现数据竞争。然而对于大型并行应用程序,若程序的控制流较为复杂,这些同步操作会导致出现以下问题:

(1)若分布在同一控制流中的不同基本块中的任务不存在依赖关系或仅存在部分依赖关系,由于基本块末尾存在同步操作,时间序列上靠后的基本块中的所有任务必须等待靠前的基本块中的所有任务执行完毕后方可参与调度。时间序列上靠后的基本块中的所有任务从进入就绪状态到实际被运行时调度之间存在一个由块间同步操作引入的人为延迟。

(2)现代计算机采用层次缓存结构,重复使用的数据会暂存在cpu缓存中。当任务间通讯是基于共享内存模型时,存在依赖关系的任务往往会共用同一片内存区域。若存在依赖关系的任务被分布到控制流中的不同基本块中。当依赖任务所在的基本块开始执行时,被依赖任务所在基本块中的大量无关任务的执行导致缓存中的所需数据较大可能被换出,从而导致程序局部性较差。



技术实现要素:

针对现有技术的以上缺陷或改进需求,本发明提供了一种基于任务窃取的任务调度方法及系统,针对大型任务级并行应用程序,提出了基于任务依赖图驱动的任务调度思想,能够有效提高传统任务级并行应用程序的性能。

为实现上述目的,按照本发明的一个方面,提供了一种基于任务窃取的任务调度方法,包括:

将整体计算任务描述为由子任务节点与子任务节点间依赖边组成的任务依赖图,将依赖节点作为回调函数注册至被依赖节点的回调容器中;

获取所述任务依赖图中的根节点与叶子节点,为所有叶子节点添加一个虚拟依赖汇节点,所述虚拟依赖汇节点用于阻塞主线程;

为线程池中各线程分配一个无锁双端队列并置空,将所有根节点按照轮询方式放入各线程的无锁双端队列底部;

对于每个线程,若线程的无锁双端队列不为空,则从线程的无锁双端队列底部取出节点并执行节点中包含的任务,在任务执行结束后,执行节点中回调容器中的所有回调;若线程的无锁双端队列为空,则该线程尝试从其他线程的无锁双端队列顶部窃取节点,若窃取成功则将窃取的节点压入该线程的无锁双端队列底部,执行窃取节点中的任务以及窃取节点中回调容器中的所有回调;

在任务依赖图中所有节点中的任务均执行完成后,将任务依赖图中的各节点的入度恢复到原始值,并结束对主线程的阻塞。

优选地,步骤(1)具体包括以下步骤:

(1.1)定义任务依赖图对象;

(1.2)依据计算任务的属性将该计算任务划分成若干子任务,调用任务依赖图对象所提供的插入方法将各子任务添加进任务依赖图中并将各子任务封装为节点对象,返回各节点对象的指针;

(1.3)通过各节点对象的指针构造各子任务间的依赖关系,将依赖节点视作回调函数注册至被依赖节点的回调容器中,将依赖节点的入度加1,将被依赖节点的出度加1。

优选地,步骤(2)具体包括以下步骤:

(2.1)将任务依赖图中所有入度为0的节点加入根节点集合,将所有出度为0的节点加入叶子节点集合;

(2.2)对于叶子节点集合l={l1,l2,…},添加虚拟依赖汇节点virtual_sink_node,并调用virtual_sink_node->depends(l1,l2,…),所述虚拟依赖汇节点用于阻塞主线程直到所有任务完成,防止主线程提前结束。

优选地,步骤(4)具体包括以下步骤:

(4.1)对于每个线程,若线程的无锁双端队列不为空,则从线程的无锁双端队列底部取出节点并执行节点中包含的任务,在任务执行结束后,执行节点中回调容器中的所有回调,每执行一次回调,相对应的注册回调的节点的入度减1,若某一注册回调的节点的入度被减至0,则当前线程将该注册回调的节点压入当前线程的无锁双端队列底部;

(4.2)若线程的无锁双端队列为空,则该线程尝试从其他线程的无锁双端队列顶部窃取节点,若窃取成功则将窃取的节点压入该线程无锁双端队列底部,并执行步骤(4.1),否则放弃本轮cpu时间片;

(4.3)重复执行步骤(4.1)~(4.2)直至任务依赖图中所有节点中的任务均执行完成。

优选地,所述线程池中的线程数目与cpu硬件核心的数目一致。

按照本发明的另一方面,提供了一种基于任务窃取的任务调度系统,包括:

任务依赖图构造模块,用于将整体计算任务描述为由子任务节点与子任务节点间依赖边组成的任务依赖图,将依赖节点作为回调函数注册至被依赖节点的回调容器中;

预处理模块,用于获取所述任务依赖图中的根节点与叶子节点,为所有叶子节点添加一个虚拟依赖汇节点,所述虚拟依赖汇节点用于阻塞主线程;

初始化模块,用于为线程池中各线程分配一个无锁双端队列并置空,将所有根节点按照轮询方式放入各线程的无锁双端队列底部;

任务调度模块,用于对于每个线程,若线程的无锁双端队列不为空,则从线程的无锁双端队列底部取出节点并执行节点中包含的任务,在任务执行结束后,执行节点中回调容器中的所有回调;若线程的无锁双端队列为空,则该线程尝试从其他线程的无锁双端队列顶部窃取节点,若窃取成功则将窃取的节点压入该线程的无锁双端队列底部,执行窃取节点中的任务以及窃取节点中回调容器中的所有回调;

现场恢复模块,用于在任务依赖图中所有节点中的任务均执行完成后,将任务依赖图中的各节点的入度恢复到原始值,并结束对主线程的阻塞。

优选地,所述任务依赖图构造模块包括:

定义模块,用于定义任务依赖图对象;

节点封装模块,用于依据计算任务的属性将该计算任务划分成若干子任务,调用任务依赖图对象所提供的插入方法将各子任务添加进任务依赖图中并将各子任务封装为节点对象,返回各节点对象的指针;

回调注册模块,用于通过各节点对象的指针构造各子任务间的依赖关系,将依赖节点视作回调函数注册至被依赖节点的回调函数容器中,将依赖节点的入度加1,将被依赖节点的出度加1。

优选地,所述预处理模块包括:

节点划分模块,用于将任务依赖图中所有入度为0的节点加入根节点集合,将所有出度为0的节点加入叶子节点集合;

虚拟依赖汇节点构造模块,用于对于叶子节点集合l={l1,l2,…},添加虚拟依赖汇节点virtual_sink_node,并调用virtual_sink_node->depends(l1,l2,…),所述虚拟依赖汇节点用于阻塞主线程直到所有任务完成,防止主线程提前结束。

优选地,所述任务调度模块包括:

任务执行模块,用于对于每个线程,若线程的无锁双端队列不为空,则从线程的无锁双端队列底部取出节点并执行节点中包含的任务,在任务执行结束后,执行节点中回调容器中的所有回调,每执行一次回调,相对应的注册回调的节点的入度减1,若某一注册回调的节点的入度被减至0,则当前线程将该注册回调的节点压入当前线程的无锁双端队列底部;

任务窃取模块,用于在线程的无锁双端队列为空时,该线程尝试从其他线程的无锁双端队列顶部窃取节点,若窃取成功则将窃取的节点压入该线程无锁双端队列底部,并取出窃取的节点进行执行,否则放弃本轮cpu时间片;重复执行所述任务执行模块以及所述任务窃取模块的操作直至任务依赖图中所有节点中的任务均执行完成。

优选地,所述线程池中的线程数目与cpu硬件核心的数目一致。

总体而言,通过本发明所构思的以上技术方案与现有技术相比,主要有以下的技术优点:

(1)使用回调机制来构建任务间依赖关系,不存在任何实体用以表示任务间的依赖边,任务依赖图中的依赖节点仅用向被依赖节点注册回调表示依赖关系。该特征可以用来减少任务依赖图的实现复杂度,任务节点仅可以由任务实体、引用计数与回调容器组成。

(2)采用任务窃取方法实现负载均衡。

(3)在所有任务执行完后方便地将任务依赖图恢复原状。假设一个任务依赖图中有v个节点与e条依赖边,则构造任务依赖图的过程的时间及空间复杂度均为o(v+e)。当任务节点较多或节点间关系较为复杂时,任务依赖图的构造将会是一个较为耗时及耗费空间的过程。现实问题往往规模较大,若将已构造好的任务依赖图重复利用,则可大大降低任务依赖图构造的成本所占的比重。每次重用前用户更换数据源即可实现“一个构造,多次使用”。

(4)使用严格基于任务依赖图的任务调度方法:从根节点开始,当一个任务节点中的任务执行完毕时,会将已就绪的子节点立即压入就绪队列准备投入执行。通过这种方式减少由于程序中的控制流而引入的不必要的延迟。并且父节点与子节点间一般存在数据上的依赖关系,此种调度方法可将子节点调度至由父节点所处线程来执行,从而尽可能利用已由父节点缓存在cpucache中的数据,减少访存次数,提高性能。

附图说明

图1为本发明实施例公开的一种基于任务窃取的任务调度方法的流程示意图;

图2为本发明实施例公开的一种对任务依赖图进行预处理的流程示意图;

图3为本发明实施例公开的一种无锁双端队列与节点的数据结构示意图;

图4为本发明实施例公开的一种负载均衡方法的流程示意图。

具体实施方式

为了使本发明的目的、技术方案及优点更加清楚明白,以下结合附图及实施例,对本发明进行进一步详细说明。应当理解,此处所描述的具体实施例仅仅用以解释本发明,并不用于限定本发明。此外,下面所描述的本发明各个实施方式中所涉及到的技术特征只要彼此之间未构成冲突就可以相互组合。

如图1所示为本发明实施例公开的一种基于任务窃取的任务调度方法的流程示意图,包括以下步骤:

(1)构造任务依赖图:将整体计算任务描述为由子任务节点与子任务节点间依赖边组成的任务依赖图,将依赖节点作为回调函数注册至被依赖节点的回调容器中;

作为一种可选的实施方式,构造任务依赖图具体包括以下步骤:

(1.1)定义任务依赖图task_graph对象;

(1.2)依据计算任务的属性将该计算任务划分成若干子任务,调用任务依赖图对象task_graph所提供的插入方法将各子任务添加进任务依赖图task_graph中并将各子任务封装为节点对象,返回各节点对象的指针;具体而言,task_graph内部会持有一份对插入方法所传入的任务的拷贝并对其加一层封装构成node对象。在调用结束后task_graph会返回node对象的指针以供后续操作。

(1.3)通过各节点对象的指针构造各子任务间的依赖关系,将依赖节点视作回调函数注册至被依赖节点的回调容器中,将依赖节点的入度加1,将被依赖节点的出度加1。具体而言,在获得node对象的指针以后,通过该指针调用depends方法以构造任务间依赖关系,假设存在nodea在逻辑上依赖于nodeb、nodec、…等节点的完成,则显式调用a->depends(b,c,…)。depends方法在内部会将nodea的执行部分以回调的形式注册至nodeb、nodec、…等节点的回调容器中。

(2)对任务依赖图进行预处理:获取所述任务依赖图中的根节点与叶子节点,为所有叶子节点添加一个虚拟依赖汇节点,所述虚拟依赖汇节点用于阻塞主线程;

作为一种可选的实施方式,步骤(2)具体包括以下步骤:

(2.1)将任务依赖图中所有入度为0的节点加入根节点集合,将所有出度为0的节点加入叶子节点集合;

(2.2)对于叶子节点集合l={l1,l2,…},添加虚拟依赖汇节点virtual_sink_node,并调用virtual_sink_node->depends(l1,l2,…),所述虚拟依赖汇节点用于阻塞主线程直到所有任务完成,防止主线程提前结束。

(3)初始化运行环境:为线程池中各线程分配一个无锁双端队列并置空,将所有根节点按照轮询方式放入各线程的无锁双端队列底部;

作为一种可选的实施方式,线程池中的线程数目与cpu硬件核心的数目一致。

(4)对任务依赖图中的所有任务进行执行:对于每个线程,若线程的无锁双端队列不为空,则从线程的无锁双端队列底部取出节点并执行节点中包含的任务,在任务执行结束后,执行节点中回调容器中的所有回调;若线程的无锁双端队列为空,则该线程尝试从其他线程的无锁双端队列顶部窃取节点,若窃取成功则将窃取的节点压入该线程的无锁双端队列底部,执行窃取节点中的任务以及窃取节点中回调容器中的所有回调;

作为一种可选的实施方式,步骤(4)具体包括以下步骤:

(4.1)对于每个线程,若线程的无锁双端队列不为空,则从线程的无锁双端队列底部取出节点并执行节点中包含的任务,在任务执行结束后,执行节点中回调容器中的所有回调,每执行一次回调,相对应的注册回调的节点的入度减1,若某一注册回调的节点的入度被减至0,则当前线程将该注册回调的节点压入当前线程的无锁双端队列底部;

(4.2)若线程的无锁双端队列为空,则该线程尝试从其他线程的无锁双端队列顶部窃取节点,若窃取成功则将窃取的节点压入该线程无锁双端队列底部,并执行步骤(4.1),否则放弃本轮cpu时间片;

(4.3)重复执行步骤(4.1)~(4.2)直至任务依赖图中所有节点中的任务均执行完成。

(5)恢复现场:在任务依赖图中所有节点中的任务均执行完成后,将任务依赖图中的各节点的入度恢复到原始值,并结束对主线程的阻塞。

总体而言,本发明提出的基于任务窃取的任务调度方法应用于任务级编程模型,实现过程为:首先定义一个task_graph对象用以表示任务依赖图,根据整体计算任务的实际情况将整体计算任务划分成大量子任务并添加进task_graph对象中并由其将任务封装为node对象托管。在任务依赖图的构造过程中,通过task_graph对象提供的depends方法指定一段依赖关系中的依赖对象与被依赖对象。在depends方法的调用过程中会将依赖对象的执行部分以回调的形式注册至被依赖对象中的回调容器中,并分别将依赖对象与被依赖对象的入度和出度加1,其中由于入度可能会被多个线程读写所以将其设为原子变量。在任务依赖图构造完成后,调用task_graph对象提供的start方法开始任务依赖图预处理与计算流程执行过程。在预处理过程,运行时首先将task_graph中受托管的所有node节点的入度信息进行备份。第二部运行时在task_graph中受托管的所有node节点中挑选出所有根节点与叶子节点,其中根节点的数目并不受限制,并为所有叶子节点添加一个虚拟依赖汇节点。此虚拟依赖汇节点用户不可见,其作用是阻塞主线程防止其在计算任务全部结束前结束,并在所有计算任务结束后依据先前备份的任务依赖图信息将任务依赖图恢复原状以供重复使用,参考图2所示为本发明实施例公开的一种对任务依赖图进行预处理的流程示意图。

如图3所示为本发明实施例公开的一种无锁双端队列与节点的数据结构示意图。在初始化阶段。运行时将会根据计算机的实际硬件核心数目分配线程,线程与核心之间一一对应,并为每一个线程分配一个私有的无锁双端队列。根节点集合中的所有根节点将会依照轮询的方式依次压入到各个线程的双端队列中的底部。此后所有线程不断从双端队列的底部取出节点并执行其中包含的任务。在节点中任务执行完毕后,线程会依次拾取节点中回调容器中注册的所有回调。依据某个节点所依赖节点的数目,其回调可能被执行多次。节点所注册的回调中增加了对临界条件的判断:回调每执行一次,注册回调的节点的入度会减1,当某一节点的入度被减至0时,代表其所依赖的所有节点已完成执行,其可被立即投入运行,回调中采用的方式是将该节点立即压入当前线程所有的双端队列的底部。

如图4所示为本发明实施例公开的一种负载均衡方法的流程示意图。基于任务依赖图的调度方式实际上会使得每个线程执行的是任务依赖图中的一个子图,但是依据子图规模与任务量,有的子图执行时间较长,有的子图执行时间较短,会造成整体计算时间由执行时间最久的线程决定,同时也会造成负载不均衡。本发明采用任务窃取算法以实现负载均衡,其步骤如下:

1)若属于线程的无锁双端队列不为空,则线程从无锁双端队列底部取出节点并执行;否则跳至步骤2)。

2)若属于线程的无锁双端队列为空,则依照轮询的方式访问其他各线程的无锁双端队列,若发现有的线程的无锁双端队列不为空,则尝试从其无锁双端队列顶部的窃取节点并压入自己的无锁双端队列底部,执行步骤1);否则跳至步骤3)。

3)放弃本轮cpu时间片,线程进入休眠状态,待下次被唤醒时,执行步骤1)。

本发明实施例还提供了一种基于任务窃取的任务调度系统,包括:

任务依赖图构造模块,用于将整体计算任务描述为由子任务节点与子任务节点间依赖边组成的任务依赖图,将依赖节点作为回调函数注册至被依赖节点的回调容器中;

预处理模块,用于获取所述任务依赖图中的根节点与叶子节点,为所有叶子节点添加一个虚拟依赖汇节点,所述虚拟依赖汇节点用于阻塞主线程;

初始化模块,用于为线程池中各线程分配一个无锁双端队列并置空,将所有根节点按照轮询方式放入各线程的无锁双端队列底部;

任务调度模块,用于对于每个线程,若线程的无锁双端队列不为空,则从线程的无锁双端队列底部取出节点并执行节点中包含的任务,在任务执行结束后,执行节点中回调容器中的所有回调;若线程的无锁双端队列为空,则该线程尝试从其他线程的无锁双端队列顶部窃取节点,若窃取成功则将窃取的节点压入该线程的无锁双端队列底部,执行窃取节点中的任务以及窃取节点中回调容器中的所有回调;

现场恢复模块,用于在任务依赖图中所有节点中的任务均执行完成后,将任务依赖图中的各节点的入度恢复到原始值,并结束对主线程的阻塞。

在本发明实施例中,各功能模块的具体实现方式可以参考方法实施例中的描述,本发明实施例将不作复述。

本发明采用上述方案,在性能上优于其他并行算法方案,并且在并行程序性能上得到很大的提升,具体如下:

1)单纯基于任务依赖图对任务进行调度,避免了人为设定的控制流造成的任务执行延迟;

2)线程被设定为与核心一一对应,运行时将存在依赖关系的节点尽量放至同一线程中运行,因此后一任务可尽量利用由前一任务使用的过、存在于cpu缓存中的数据,减少访问内存的次数。

3)采用任务窃取算法实现负载均衡。

本领域的技术人员容易理解,以上所述仅为本发明的较佳实施例而已,并不用以限制本发明,凡在本发明的精神和原则之内所作的任何修改、等同替换和改进等,均应包含在本发明的保护范围之内。

当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1