本發明屬于計算機并行程序設計技術領域,更具體地,涉及一種基于任務竊取的任務調度方法及系統。
背景技術:
不斷逼近物理極限的晶體管尺寸以及功耗嚴重限制了計算機中單核處理器的發展,再也無法像以前一樣只用等待芯片制造商推出新的處理器就能獲得程序性能的提升。為進一步提升應用程序性能,只有依賴將多個核心集成到單個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)采用任務竊取算法實現負載均衡。
本領域的技術人員容易理解,以上所述僅為本發明的較佳實施例而已,并不用以限制本發明,凡在本發明的精神和原則之內所作的任何修改、等同替換和改進等,均應包含在本發明的保護范圍之內。