點(diǎn)內(nèi)部嵌套算法與嵌套流圖)
并發(fā)編程高性能計(jì)算【免費(fèi)下載鏈接】oneTBBoneAPI Threading Building Blocks (oneTBB)項(xiàng)目地址https://gitcode.com/gh_mirrors/on/oneTBB點(diǎn)擊查看免費(fèi)下載導(dǎo)讀本文圍繞 oneTBBoneAPI Threading Building BlocksFlow Graph 的嵌套并行技巧展開講解兩種核心做法在節(jié)點(diǎn)體內(nèi)嵌套其他并行算法如parallel_for以提升可擴(kuò)展性以及在節(jié)點(diǎn)體內(nèi)構(gòu)造并執(zhí)行嵌套的流圖。讀完本文后你將掌握如何把 Flow Graph 當(dāng)作一種協(xié)調(diào)語言——在圖層面表達(dá)粗粒度并行在節(jié)點(diǎn)內(nèi)部表達(dá)細(xì)粒度并行——并學(xué)會在嵌套圖結(jié)構(gòu)不變時(shí)通過復(fù)用持久化圖來消除不必要的重建開銷。原文出自倉庫文檔 Flow_Graph_nested_parallelism_tips.rst包含 use_nested_algorithms.rst 與 use_nested_flow_graphs.rst 兩篇姊妹篇。為什么要嵌套并行把 Flow Graph 當(dāng)作協(xié)調(diào)語言提高 Flow Graph 可擴(kuò)展性的一條強(qiáng)大途徑是在節(jié)點(diǎn)體node body內(nèi)部嵌套其他并行算法。這樣做的意義在于你可以把 Flow Graph 用作一種協(xié)調(diào)語言coordination language——在圖的層面表達(dá)最粗粒度的并行在節(jié)點(diǎn)內(nèi)部嵌套更細(xì)粒度的并行從而把任務(wù)如何組合與單個(gè)任務(wù)內(nèi)部如何并行兩個(gè)層次解耦。從實(shí)現(xiàn)上看這種設(shè)計(jì)之所以可行是因?yàn)?Flow Graph 的節(jié)點(diǎn)執(zhí)行與底層 oneTBB 任務(wù)調(diào)度器深度融合節(jié)點(diǎn)體最終被封裝為圖任務(wù)graph task提交到與圖關(guān)聯(lián)的任務(wù)競技場task arena中執(zhí)行見 src/tbb/task_dispatcher.cpp 與 include/oneapi/tbb/detail/_flow_graph_impl.h 中的graph_task。因此當(dāng)節(jié)點(diǎn)體內(nèi)部再調(diào)用parallel_for等并行算法時(shí)這些算法生成的子任務(wù)會被同一調(diào)度器無縫接納實(shí)現(xiàn)嵌套并行的自然展開。技巧一在節(jié)點(diǎn)體內(nèi)嵌套并行算法場景與圖結(jié)構(gòu)文檔給出的示例是一個(gè)典型的生產(chǎn)者–變換–消費(fèi)者流水線由五個(gè)節(jié)點(diǎn)組成一個(gè)input_nodematrix_source從一個(gè)文件按順序讀取矩陣序列兩個(gè)function_noden1、n2接收矩陣并對每個(gè)元素應(yīng)用一個(gè)函數(shù)f1/f2生成兩個(gè)新矩陣兩個(gè)終結(jié)點(diǎn)function_noden1_sink、n2_sink分別消費(fèi)n1和n2產(chǎn)出的結(jié)果矩陣。連接關(guān)系為matrix_source同時(shí)連接n1與n2廣播n1連接n1_sinkn2連接n2_sink。其中read_next_matrix、f1、f2、consume_f1、consume_f2等函數(shù)在原文中未給出需由讀者自行實(shí)現(xiàn)例如對矩陣逐元素應(yīng)用f1后返回新矩陣consume_*負(fù)責(zé)釋放或匯總結(jié)果。完整代碼示例以下代碼完整復(fù)刻原文示例其中的關(guān)鍵點(diǎn)是在n1和n2的 lambda 表達(dá)式中嵌套了parallel_for對矩陣的每個(gè)元素并行執(zhí)行變換graph g; input_node double * matrix_source( g, - double* { double *a read_next_matrix(); if ( a ) { return a; } else { fc.stop(); return nullptr; } } ); function_node double *, double * n1( g, unlimited, - double * { double *b new double[N]; parallel_for( 0, N, { b[i] f1(a[i]); } ); return b; } ); function_node double *, double * n2( g, unlimited, - double * { double *b new double[N]; parallel_for( 0, N, { b[i] f2(a[i]); } ); return b; } ); function_node double *, double * n1_sink( g, unlimited, []( double *b ) - double * { return consume_f1(b); } ); function_node double *, double * n2_sink( g, unlimited, []( double *b ) - double * { return consume_f2(b); } ); make_edge( matrix_source, n1 ); make_edge( matrix_source, n2 ); make_edge( n1, n1_sink ); make_edge( n2, n2_sink ); matrix_source.activate(); g.wait_for_all();關(guān)鍵 API 的底層語義input_node與flow_controlinput_node是無前驅(qū)、只做數(shù)據(jù)源的可執(zhí)行節(jié)點(diǎn)定義于 include/oneapi/tbb/flow_graph.h。它創(chuàng)建后默認(rèn)處于未激活狀態(tài)需要顯式調(diào)用activate()才會開始向后繼節(jié)點(diǎn)投遞消息——這正是示例末尾matrix_source.activate()的含義。節(jié)點(diǎn)體接收一個(gè)oneapi::tbb::flow_control 參數(shù)其實(shí)現(xiàn)位于 include/oneapi/tbb/detail/_pipeline_filters.h內(nèi)部維護(hù)bool is_pipeline_stopped調(diào)用fc.stop()即置位該標(biāo)志input_node在調(diào)用完節(jié)點(diǎn)體后檢查該標(biāo)志若已停止則不再緩存/投遞本次返回的nullptr見try_reserve_apply_body中對control.is_pipeline_stopped的判斷flow_graph.h。因此示例中讀到結(jié)尾返回nullptr并fc.stop()是標(biāo)準(zhǔn)的停止輸入流的模式。unlimited并發(fā)度function_node的構(gòu)造參數(shù)concurrency決定節(jié)點(diǎn)體可以被多少個(gè)并發(fā)調(diào)用。unlimited與serial定義于 include/oneapi/tbb/flow_graph.henum concurrency { unlimited 0, serial 1 };。使用unlimited意味著每個(gè)到達(dá)的消息都可以立即啟動(dòng)一個(gè)新的節(jié)點(diǎn)體執(zhí)行節(jié)點(diǎn)之間、以及節(jié)點(diǎn)體內(nèi)部的嵌套并行算法之間可以充分重疊——這是本例中兩個(gè)矩陣分支能夠并行推進(jìn)、同時(shí)每個(gè)矩陣內(nèi)部又能再拆成多路并行的前提。function_node的構(gòu)造與內(nèi)部function_input/function_output的實(shí)現(xiàn)參見 flow_graph.h。嵌套算法的并行度疊加示例里n1與n2是unlimited的兩者可以并行各自內(nèi)部的parallel_for(0, N, ...)又會把單矩陣的逐元素變換進(jìn)一步切分給多個(gè)工作線程。整體效果是圖層面并行 × 節(jié)點(diǎn)內(nèi)部并行共同壓滿硬件線程。需要留意的是節(jié)點(diǎn)體內(nèi)創(chuàng)建的新數(shù)組b由節(jié)點(diǎn)負(fù)責(zé)釋放示例中由consume_f1/consume_f2處理這是開發(fā)者需要自行保證的內(nèi)存生命周期約定。技巧二嵌套流圖Flow Graph 內(nèi)嵌 Flow Graph除了在節(jié)點(diǎn)體內(nèi)嵌套算法還可以在節(jié)點(diǎn)體內(nèi)嵌套整個(gè)流圖外層圖g的兩個(gè)節(jié)點(diǎn)a、b各自在收到消息時(shí)構(gòu)造并執(zhí)行一個(gè)內(nèi)層圖。節(jié)點(diǎn)a收到消息后構(gòu)造并執(zhí)行一個(gè)內(nèi)層依賴圖dependence graph其節(jié)點(diǎn)以continue_msg為消息類型通過make_edge形成n1 → n2、n1 → n3、n2 → n4、n3 → n4的菱形依賴結(jié)構(gòu)節(jié)點(diǎn)b收到消息后構(gòu)造并執(zhí)行一個(gè)內(nèi)層數(shù)據(jù)流圖data flow graph由四個(gè)function_nodem1m4以相同的菱形拓?fù)溥B接并注入整數(shù)消息。完整代碼示例graph g; function_node int, int a( g, unlimited, []( int i ) - int { graph h; node_t n1( h, { cout n1: i \n; } ); node_t n2( h, { cout n2: i \n; } ); node_t n3( h, { cout n3: i \n; } ); node_t n4( h, { cout n4: i \n; } ); make_edge( n1, n2 ); make_edge( n1, n3 ); make_edge( n2, n4 ); make_edge( n3, n4 ); n1.try_put(continue_msg()); h.wait_for_all(); return i; } ); function_node int, int b( g, unlimited, []( int i ) - int { graph h; function_node int, int m1( h, unlimited, []( int j ) - int { cout m1: j \n; return j; } ); function_node int, int m2( h, unlimited, []( int j ) - int { cout m2: j \n; return j; } ); function_node int, int m3( h, unlimited, []( int j ) - int { cout m3: j \n; return j; } ); function_node int, int m4( h, unlimited, []( int j ) - int { cout m4: j \n; return j; } ); make_edge( m1, m2 ); make_edge( m1, m3 ); make_edge( m2, m4 ); make_edge( m3, m4 ); m1.try_put(i); h.wait_for_all(); return i; } ); make_edge( a, b ); for ( int i 0; i 3; i ) { a.try_put(i); } g.wait_for_all();為什么必須調(diào)用h.wait_for_all()在第一種實(shí)現(xiàn)中內(nèi)層圖h是節(jié)點(diǎn)體作用域內(nèi)的局部變量每次調(diào)用都會在離開作用域時(shí)被析構(gòu)。而 oneTBB 的graph析構(gòu)函數(shù)本身會調(diào)用wait_for_all()等待圖中所有未完成任務(wù)執(zhí)行完畢見 include/oneapi/tbb/detail/_flow_graph_impl.h 中g(shù)raph::~graph()的實(shí)現(xiàn)。因此若節(jié)點(diǎn)體結(jié)束時(shí)不顯式調(diào)用h.wait_for_all()內(nèi)層圖的析構(gòu)會隱式等待語義上仍然安全但顯式調(diào)用h.wait_for_all()能讓等待內(nèi)層圖空閑這一意圖更加清晰并讓b的節(jié)點(diǎn)體阻塞至內(nèi)層圖完成從而保證返回i時(shí)內(nèi)層圖已徹底結(jié)束便于測試與排錯(cuò)。graph::wait_for_all的實(shí)現(xiàn)依賴于圖內(nèi)置的等待計(jì)數(shù)機(jī)制reserve_wait/release_wait見 flow_graph_impl.h圖不會從wait_for_all返回直到所有reserve_wait都有對應(yīng)的release_wait與之匹配。依賴圖 vs 數(shù)據(jù)流圖兩種內(nèi)層圖的差異上述示例有意展示了兩種內(nèi)層圖依賴圖節(jié)點(diǎn)以continue_msg驅(qū)動(dòng)n1.try_put(continue_msg())。continue_msg在 flow_graph.h 中定義為一個(gè)空標(biāo)記類型continue_receiver則負(fù)責(zé)維護(hù)前驅(qū)計(jì)數(shù)只有所有前驅(qū)都投遞過continue_msg后節(jié)點(diǎn)才會執(zhí)行flow_graph.h。它表達(dá)的語義是依賴滿足即觸發(fā)適合建模 DAG 式的任務(wù)依賴關(guān)系數(shù)據(jù)流圖節(jié)點(diǎn)以真實(shí)的整數(shù)消息驅(qū)動(dòng)m1.try_put(i)消息沿邊逐級變換傳遞適合建模數(shù)據(jù)加工管線。兩者都是嵌套圖的有效形態(tài)選擇哪一種取決于內(nèi)層任務(wù)之間的耦合方式是依賴關(guān)系還是數(shù)據(jù)流動(dòng)。技巧三復(fù)用持久化內(nèi)層圖消除重建開銷如果嵌套圖的結(jié)構(gòu)在節(jié)點(diǎn)的多次調(diào)用之間保持不變那么每次調(diào)用都重新構(gòu)造一遍圖是冗余的——重建只會在執(zhí)行上增加不必要的開銷。文檔對此給出了優(yōu)化方案把內(nèi)層圖提升為外層作用域的持久對象節(jié)點(diǎn)b每次調(diào)用時(shí)直接向已存在的圖投遞消息。復(fù)用版完整代碼示例graph h; function_node int, int m1( h, unlimited, []( int j ) - int { cout m1: j \n; return j; } ); function_node int, int m2( h, unlimited, []( int j ) - int { cout m2: j \n; return j; } ); function_node int, int m3( h, unlimited, []( int j ) - int { cout m3: j \n; return j; } ); function_node int, int m4( h, unlimited, []( int j ) - int { cout m4: j \n; return j; } ); make_edge( m1, m2 ); make_edge( m1, m3 ); make_edge( m2, m4 ); make_edge( m3, m4 ); graph g; function_node int, int a( g, unlimited, []( int i ) - int { graph h; node_t n1( h, { cout n1: i \n; } ); node_t n2( h, { cout n2: i \n; } ); node_t n3( h, { cout n3: i \n; } ); node_t n4( h, { cout n4: i \n; } ); make_edge( n1, n2 ); make_edge( n1, n3 ); make_edge( n2, n4 ); make_edge( n3, n4 ); n1.try_put(continue_msg()); h.wait_for_all(); return i; } ); function_node int, int b( g, unlimited, - int { m1.try_put(i); h.wait_for_all(); // 可選h 不會被析構(gòu) return i; } ); make_edge( a, b ); for ( int i 0; i 3; i ) { a.try_put(i); } g.wait_for_all();復(fù)用后wait_for_all變得可選文檔特別指出在復(fù)用版中只有當(dāng)你希望b的節(jié)點(diǎn)體阻塞等待內(nèi)層圖執(zhí)行完畢時(shí)才需要在每次調(diào)用末尾調(diào)用h.wait_for_all()。因?yàn)樵诘谝话鎸?shí)現(xiàn)中圖h在離開作用域時(shí)被析構(gòu)析構(gòu)會隱式等待而復(fù)用版中h是持久對象不會在調(diào)用結(jié)束時(shí)被銷毀所以即使只調(diào)用m1.try_put(i)后直接返回、不等待h變?yōu)榭臻e也是合法的——內(nèi)層圖的消息會在后臺繼續(xù)執(zhí)行。這一優(yōu)化有兩個(gè)實(shí)踐要點(diǎn)生命周期管理持久圖h的存活時(shí)間必須覆蓋外層圖g的全部使用周期例如兩者都定義為main作用域內(nèi)的局部變量或具有合適的對象生命周期避免懸垂引用并發(fā)安全如果外層圖允許b的多個(gè)副本并發(fā)執(zhí)行unlimited并發(fā)度那么對同一個(gè)持久內(nèi)層圖h的并發(fā)try_put需要自行評估其線程安全性若需要串行化對內(nèi)層圖的訪問應(yīng)把b的并發(fā)度設(shè)為serial或在內(nèi)層圖訪問外加鎖。測試與驗(yàn)證依據(jù)倉庫中與本文主題相關(guān)的驗(yàn)證素材包括流圖節(jié)點(diǎn)基礎(chǔ)行為測試test/tbb/test_flow_graph.cpp 與 test/tbb/test_function_node.cpp覆蓋function_node、input_node、make_edge的消息投遞與并發(fā)語義嵌套復(fù)合節(jié)點(diǎn)測試test/tbb/test_composite_node.cpp 中的test_nested_adderL296驗(yàn)證了復(fù)合節(jié)點(diǎn)內(nèi)部再嵌套節(jié)點(diǎn)的場景可作為嵌套結(jié)構(gòu)用法的補(bǔ)充參考嵌套并行與任務(wù)上下文測試test/tbb/test_eh_algorithms.cpp 討論了嵌套parallel_for/parallel_reduce與任務(wù)組上下文的關(guān)系test/tbb/test_arena_priorities.cpp 則包含嵌套 arena相關(guān)場景的測試L355-L372印證了 oneTBB 對多層嵌套并行執(zhí)行的支持。小結(jié)oneTBB Flow Graph 的嵌套并行提供了兩種互補(bǔ)的擴(kuò)展手段手段適用場景關(guān)鍵注意點(diǎn)節(jié)點(diǎn)體內(nèi)嵌套并行算法parallel_for等單個(gè)節(jié)點(diǎn)內(nèi)部存在可并行的細(xì)粒度計(jì)算節(jié)點(diǎn)并發(fā)度設(shè)為unlimited以充分重疊注意節(jié)點(diǎn)體內(nèi)內(nèi)存的分配與釋放節(jié)點(diǎn)體內(nèi)嵌套流圖節(jié)點(diǎn)需要表達(dá)一組內(nèi)部任務(wù)之間的依賴/數(shù)據(jù)關(guān)系內(nèi)層圖被析構(gòu)時(shí)會隱式wait_for_all顯式等待以阻塞節(jié)點(diǎn)體復(fù)用持久化內(nèi)層圖內(nèi)層圖結(jié)構(gòu)在多次調(diào)用間不變僅在需要阻塞時(shí)調(diào)用h.wait_for_all()注意生命周期與并發(fā)訪問把粗粒度并行交給圖的拓?fù)浒鸭?xì)粒度并行交給節(jié)點(diǎn)體內(nèi)的算法再把結(jié)構(gòu)固定的嵌套圖持久化復(fù)用——這套組合拳能讓你在保持圖結(jié)構(gòu)清晰的同時(shí)最大化硬件資源的利用率。更多流圖與嵌套并行的背景知識可繼續(xù)閱讀倉庫中的 Flow_Graph.rst、Nodes.rst 與 Guiding_Task_Scheduler_Execution.rst 等文檔。贊分享并發(fā)編程高性能計(jì)算【免費(fèi)下載鏈接】oneTBBoneAPI Threading Building Blocks (oneTBB)項(xiàng)目地址https://gitcode.com/gh_mirrors/on/oneTBB點(diǎn)擊查看免費(fèi)下載相關(guān)推薦FAIR Chemistry 生成模型全景ADiT、FlowMM、FlowLLM 與 Crystal-text-llm 的分子與材料生成技術(shù)指南FAIR Chemistry 生成模型全景ADiT、FlowMM、FlowLLM 與 Crystal text llm 的分子與材料生成技術(shù)指南 本文系統(tǒng)梳理并發(fā)編程高性能計(jì)算mold 內(nèi)嵌 oneTBB 并行基石task_group_context 取消與嵌套并行深度解析mold 內(nèi)嵌 oneTBB 并行基石task_group_context 取消與嵌套并行深度解析 mold 鏈接器的并行加速高度依賴內(nèi)嵌的 oneTBB見開發(fā)工具構(gòu)建工具系統(tǒng)編程oneTBB 嵌套并行取消機(jī)制詳解task_group_context、隔離上下文與流圖取消傳播oneTBB 嵌套并行取消機(jī)制詳解task_group_context、隔離上下文與流圖取消傳播 導(dǎo)讀 本文圍繞 oneTBBoneAPI Threadin并發(fā)編程高性能計(jì)算上一篇Changes架構(gòu)設(shè)計(jì)原理分布式構(gòu)建協(xié)調(diào)系統(tǒng)實(shí)現(xiàn)詳解 下一篇DLSS Swapper 完整指南:游戲 DLSS 版本管理一鍵切換不折騰創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考