关于Numba的线程实现的说明
關于Numba的線程實現的說明
由Numbaparallel目標執行的工作由Numba線程層執行。實際上,“線程層”是Numba內置庫,可以執行所需的并發執行。在撰寫本文時,有三個可用的線程層,每個線程層都通過不同的較低級別的host線程庫實現。上thread線程和對于給定的應用/系統的thread線程的適當選擇的更多信息可在發現 threading layer documentatio。
以下各節要關注的相關信息是,執行并行線程庫中的parallel_for函數。此功能的工作是協調和執行并行任務。
本文引用的相關源文件是
? numba/np/ufunc/tbbpool.cpp
? numba/np/ufunc/omppool.cpp
? numba/np/ufunc/workqueue.c
這些文件分別包含TBB,OpenMP和工作隊列線程池實現。每個函數都包含函數 set_num_threads(),get_num_threads()和和get_thread_id(),以及在各自的調度程序中用于線程屏蔽的相關邏輯。請注意,基本線程局部變量邏輯在這些文件中都是重復的,并且不在它們之間共享。
? numba/np/ufunc/parallel.py
該文件包含了Python和JIT兼容的封裝器 set_num_threads(),get_num_threads()和get_thread_id(),以及代碼加載上述庫到Python和啟動線程池。
? numba/parfors/parfor_lowering.py
該文件包含用于為并行后端生成代碼的主要邏輯。在生成調度程序代碼的代碼中訪問該線程掩碼,并將其傳遞給相關的后端調度程序功能。
線程屏蔽
作為其設計的一部分,Numba絕不會在numba.np.ufunc.parallel._launch_threads() 運行首次并行執行時,最初啟動的線程之外啟動新線程。這是由于在實施線程屏蔽之前已在Numba中實現了線程。保留此限制是為了使設計簡單,盡管將來可以刪除它。因此,可以以編程方式設置線程數,但只能設置為小于或等于已啟動的總數。這是通過“屏蔽”未使用的線程來完成的,從而使它們不起作用。例如,在16核計算機上,如果用戶要調用set_num_threads,則Numba將始終存在16個線程,但是其中12個線程將處于空閑狀態以進行并行計算。進一步調用 set_num_threads(16) ,導致這些相同的線程在以后的計算中起作用。
添加了線程掩碼,使用戶可以以編程方式更改在線程層中執行工作的線程數。事實證明,線程屏蔽難以實現,需要開發一種適合用戶,易于推理且可以安全實現的編程模型,并在各個線程層上具有一致的行為。
編程模型
選擇的編程模型與OpenMP中的模型相似。做出此選擇的原因是,它對于許多用戶來說是熟悉的,范圍有限且簡單。通過調用指定正在使用 set_num_threads的線程數,可以通過調用查詢正在使用的線程數 get_num_threads。這兩個函數與它們的OpenMP對應(與上述限制相同,即掩碼必須小于或等于已啟動的線程)。執行語義也與OpenMP相似,因為一旦啟動并行區域,更改線程掩碼不會對當前正在執行的區域產生影響,但會對隨后執行的并行區域產生影響。
實施
除了對線程層庫中已經存在的用戶代碼進行限制以外,對用戶代碼沒有其它限制,需要仔細考慮線程掩碼的設計。無法將“線程掩碼”存儲在全局值中,因為同時使用線程層,可能會導致值本身出現競爭形式。涉及具有這種全局價值的各種互斥量的眾多設計,最終僅通過理想實驗就打破了所有這些互斥量。最終發現,在某些OpenMP實現之后,“線程掩碼”最好以a實現。這意味著每個執行Numba并行函數的線程,都將具有一個線程本地存儲(TLS)插槽,其中包含在線程中調度線程thread localparallel_for時,要使用的線程掩碼的值。
TLS使用一個線程掩模的上述概念是相對容易實現的, get_num_threads和set_num_threads簡單地需要解決的TLS時隙在給定的線程層。這也意味著可以從運行時調用中得出并行區域的執行調度get_num_threads。這是通過眾所周知的且相對容易實現的C 庫函數注冊模式,并將其包裝在內部Numba來實現的。
除了滿足原始的前期線程屏蔽要求之外,還需要考慮以下一些更復雜的方案。
嵌套并行
在所有線程層中,“主線程”將調用該parallel_for 函數,然后在并行區域中,根據線程層的不同,一些其它線程將有助于完成實際工作。如果工作包含對另一個并行函數的調用(即嵌套并行性),則使調用的線程必須知道主線程的“線程掩碼”是什么,以便可以將其傳遞到主線程中。 parallel_for在執行嵌套并行函數時調用。此行為的實現是特定于線程層的,但一般原則是“主線程”始終將線程掩碼的值從其TLS插槽“發送”到線程層中,在并行區域中處于活動狀態的所有線程。這些活動線程將在執行任何工作之前,使用此值更新其TLS插槽。該實現細節的最終結果是:
? 線程掩碼正確傳遞到嵌套函數中
? 并行區域中的每個線程仍然可以安全地使用不同的掩碼來調用嵌套函數,如果未明確設置,則使用從“主線程”繼承的掩碼
? parallel_for成功容納具有動態調度,且線程可能在執行期間加入和離開活動池的線程層
? 任何“主線程”線程掩碼都與活動線程池中線程的線程掩碼的流入特性完全分離
Python線程獨立調用并行函數
嚴格保護線程層啟動順序,確保啟動既是線程安全的,又是進程安全的,并且每個進程運行一次。在具有大量threading都使用Numba的Python模塊線程的系統中,啟動序列中的第一個線程將正確設置其線程掩碼,但是沒有其他線程可以運行啟動序列。這意味著其它線程將需要以其它方式設置其初始線程掩碼。這是在get_num_threads調用,且不存在線程掩碼時實現的,在這種情況下,線程掩碼將設置為默認值。在該實現中,“不存在線程掩碼”由值表示,-1。“默認線程掩碼”(未設置)由值表示0。執行set_num_threads(NUMBA_NUM_THREADS),此算子也會立即調用,因此如果有-1或0,由于遇到此結果get_num_threads(),而表示上述過程中存在錯誤。
操作系統fork()調用
使用TLS也是在由Linux(用于Numba使用最流行的平臺)驅動,將TLS傳輸到子進程fork(2, 3P)clone(2)CLONE_SETTLS。
線程ID
私有get_thread_id()函數被添加到每個線程后端,該函數為每個線程返回唯一的ID。可以通過以下方式從Python訪問 numba.np.ufunc.parallel._get_thread_id()(也可以在JIT編譯函數中使用它)。線程ID函數對于測試線程屏蔽行為是否正確很有用,但不應在測試之外使用。例如,可以調用set_num_threads,收集_get_thread_id()并行區域中的所有唯一性,驗證僅運行了4個線程。
注意事項
測試線程屏蔽時需要注意的一些注意事項:
? TBB后端可以選擇調度少于給定掩碼數的線程。因此,諸如上述測試,可能返回少于4個唯一線程。
? 工作隊列后端不是線程安全的,因此嘗試對其執行多線程嵌套并行處理,可能會導致死鎖或其他未定義的行為。如果工作隊列后端檢測到嵌套的并行性,它將發出SIGABRT信號。
? 某些后端可能會重用主線程進行計算,但是不應依賴此行為(例如,如果傳遞異常)。
在代碼生成中使用
get_num_threads在代碼生成中使用的一般模式是
import llvmlite.llvmpy.core as lc
get_num_threads = builder.module.get_or_insert_function(
lc.Type.function(lc.Type.int(types.intp.bitwidth), []),
name=“get_num_threads”)
num_threads = builder.call(get_num_threads, [])
with cgutils.if_unlikely(builder, builder.icmp_signed(’<=’, num_threads,
num_threads.type(0))):
cgutils.printf(builder, “num_threads: %d\n”, num_threads)
context.call_conv.return_user_exc(builder, RuntimeError,
("Invalid number of threads. "
“This likely indicates a bug in Numba.”,))
# Pass num_threads through to the appropriate backend function here
請參閱中的代碼numba/parfors/parfor_lowering.py。
num_threads嚴格禁止<= 0的防護措施是必要的,但是在線程屏蔽邏輯包含錯誤的情況下,它可以防止意外的錯誤行為。
該num_threads變量應傳遞給適當的后端函數,例如do_scheduling或parallel_for。如果以其它方式使用(而不是將其傳遞給后端函數),應考慮上述注意事項,確保num_threads安全使用變量。最好將這樣的邏輯保留在線程后端中,而不是嘗試在代碼生成中進行。
總結
以上是生活随笔為你收集整理的关于Numba的线程实现的说明的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hashing散列注意事项
- 下一篇: 缓存注意事项