版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.08 星期一 |
前言
很多做視頻和圖像的,相信對這個框架都不是很陌生,它渲染高級3D圖形,并使用GPU執行數據并行計算。接下來的幾篇我們就詳細的解析這個框架。感興趣的看下面幾篇文章。
1. Metal框架詳細解析(一)—— 基本概覽
2. Metal框架詳細解析(二) —— 器件和命令(一)
3. Metal框架詳細解析(三) —— 渲染簡單的2D三角形(一)
4. Metal框架詳細解析(四) —— 關于GPU Family 4(一)
5. Metal框架詳細解析(五) —— 關于GPU Family 4之關于Imageblocks(二)
6. Metal框架詳細解析(六) —— 關于GPU Family 4之關于Tile Shading(三)
7. Metal框架詳細解析(七) —— 關于GPU Family 4之關于光柵順序組(四)
8. Metal框架詳細解析(八) —— 關于GPU Family 4之關于增強的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細解析(九) —— 關于GPU Family 4之關于線程組共享(六)
10. Metal框架詳細解析(十) —— 基本組件(一)
11. Metal框架詳細解析(十一) —— 基本組件之器件選擇 - 圖形渲染的器件選擇(二)
12. Metal框架詳細解析(十二) —— 基本組件之器件選擇 - 計算處理的設備選擇(三)
13. Metal框架詳細解析(十三) —— 計算處理(一)
14. Metal框架詳細解析(十四) —— 計算處理之你好,計算(二)
15. Metal框架詳細解析(十五) —— 計算處理之關于線程和線程組(三)
Calculating Threadgroup and Grid Sizes - 計算線程組和網格大小
在調度計算處理工作負載時,計算線程組和網格的最佳大小。
Overview - 概覽
在iOS 11
和macOS 10.13
及更高版本中,當您準備執行計算內核代碼時,需要指定網格的大小和每個線程組的線程數。 然后,如果網格大小不是線程組大小的倍數,則Metal會計算線程組的數量并提供非均勻線程組。 這可確保您沒有未充分利用的線程。
在早期版本的iOS和macOS中,您需要指定線程組的大小和數量。 由于網格由均勻的線程組組成,因此可能與數據大小不匹配,在這種情況下,您需要向計算內核添加防御性代碼,以確保它不在數據范圍之外執行。
Calculate Threads per Threadgroup - 計算每個線程組的線程數
您可以根據兩個MTLComputePipelineState屬性計算每個線程組的線程數。一個屬性是 maxTotalThreadsPerThreadgroup(可以在單個線程組中的最大線程數)。另一個是threadExecutionWidth(計劃在GPU上并行執行的線程數)。
maxTotalThreadsPerThreadgroup
屬性取決于設備,計算內核的寄存器使用情況以及線程組內存使用情況。在創建計算管道狀態后,其maxTotalThreadsPerThreadgroup
值不會更改,但同一設備上的兩個管道狀態可能會返回不同的值。
每個線程組的線程數不能超過maxTotalThreadsPerThreadgroup
。在maxTotalThreadsPerThreadgroup
值為512
且threadExecutionWidth
為32
的設備上,每個線程組的合適線程數為32(線程執行寬度)x 16(每個線程組的總線程數除以線程執行寬度)。Listing 1
顯示了根據線程執行寬度和每個線程組的最大線程定義線程組維度的示例。
// Listing 1
Calculating threads per threadgroup.
NSUInteger w = pipelineState.threadExecutionWidth;
NSUInteger h = pipelineState.maxTotalThreadsPerThreadgroup / w;
MTLSize threadsPerThreadgroup = MTLSizeMake(w, h, 1);
Metal
能夠計算網格(在這種情況下,圖像或紋理)如何被最佳地劃分為非均勻的,任意大小的線程組。 計算命令編碼器的dispatchThreads:threadsPerThreadgroup:方法需要線程的總數 - 每個線程負責一個像素 - 以及Listing 1中計算的threadsPerThreadgroup
值,如下例所示:
MTLSize threadsPerGrid = MTLSizeMake(texture.width, texture.height, 1);
[computeCommandEncoder dispatchThreads: threadsPerGrid
threadsPerThreadgroup: threadsPerThreadgroup];
當Metal執行此計算時,它可以沿網格邊緣生成較小的線程組,如下所示。 與均勻線程組相比,此技術簡化了內核代碼并提高了GPU性能。
Calculate Threadgroups per Grid - 計算每個網格的線程組
如果需要對線程組的大小和數量進行精細控制,則可以手動計算網格的劃分方式。 在您的代碼中,確保有足夠的線程組來覆蓋整個圖像。 這是一個例子:
MTLSize threadgroupsPerGrid = MTLSizeMake((texture.width + w - 1) / w,
(texture.height + h - 1) / h,
1);
給定1024 x 768
的紋理,上面的代碼返回一個32 x 48 x 1
的MTLSize
對象,這意味著紋理被分成1536個線程組,每個線程組包含512個線程,總共786,432個線程。 在這種情況下,該數字與圖像中的像素數匹配,并且處理整個圖像時沒有未充分利用的線程。 但是,情況可能并非總是如此(例如,對于1920 x 1080
的圖像尺寸)。 此代碼通過四舍五入,確保有足夠的線程來處理整個圖像。
使用這種方法,線程組生成的網格可能比您的數據大。 因此,如果網格中的線程位置超出數據范圍,則代碼應提前退出。 下圖顯示了一組4 x 4線程組如何在網格邊界上擴展,從而導致線程利用不足:
Listing 4
顯示了一個簡單的內核,它將不透明的白色寫入outputTexture
中的每個像素。 它首先將線程位置與紋理邊界進行比較,如果位置超出紋理范圍,則return
。
// Listing 4
Exiting early when out of bounds.
kernel void
simpleKernelFunction(texture2d<float, access::write> outputTexture [[texture(0)]],
uint2 position [[thread_position_in_grid]]) {
if (position.x >= outputTexture.get_width() || position.y >= outputTexture.get_height()) {
return;
}
outputTexture.write(float4(1.0), position);
}
請注意,以前的dispatchThreads:threadsPerThreadgroup:
技術不需要進行此檢查。
使用Listing 4
中的代碼,最終的調度將是:
[computeCommandEncoder dispatchThreadgroups: threadgroupsPerGrid
threadsPerThreadgroup: threadsPerThreadgroup];
后記
本篇主要講述了計算線程組和網格大小,感興趣的給個贊或者關注~~~