2010-10-27 18 views

回答

26

Partitioner類是用來做並行執行更多的矮胖。如果你有很多非常小的任務並行運行,那麼爲每個任務調用委託的開銷可能會很高。通過使用Partitioner,可以將工作負載重新排列爲塊,並且每個並行調用都可以在稍大的集合上工作。這個類抽象出這個特性,並且能夠根據數據集和可用內核的實際情況進行分區。

示例:假設您想要像這樣並行運行一個簡單的計算。

Parallel.ForEach(Input, (value, loopState, index) => { Result[index] = value*Math.PI; }); 

這將調用Input中每個條目的委託。這樣做會增加每個開銷。通過使用Partitioner,我們可以做這樣的事情

Parallel.ForEach(Partitioner.Create(0, Input.Length), range => { 
    for (var index = range.Item1; index < range.Item2; index++) { 
     Result[index] = Input[index]*Math.PI; 
    } 
}); 

這將減少所調用的數量,因爲每個調用將在更大的一組工作。根據我的經驗,這可以在非常簡單的操作並行化時顯着提升性能。

+8

Partitioner.Create()的默認rangeSize是1。因此,這兩個代碼示例的分區是相同的。除非Partitioner.Create(0,Input.Length,i);其中i> 1,它仍然具有相同數量的線程。 – Pingpong 2014-01-24 15:31:28

2

要並行化數據源上的操作,其中一個基本步驟是將源分區爲可由多個線程同時訪問的多個部分。當您編寫並行查詢或ForEach循環時,PLINQ和任務並行庫(TPL)提供默認分區程序,這些分區程序可以透明地工作。對於更高級的場景,你可以插入你自己的分區器。

更多here

+2

我添加到:一般來說*你*不使用它。 PLINQ的確如此,你可能會逃脫默認分區。 – 2010-10-27 09:51:24

1

如Brian Rasmussen所建議的,範圍分區是一種在CPU密集型工作時應該使用的分區類型,往往很小(相對於虛擬方法調用),必須處理許多元素,並且當涉及到每個元素的運行時間時,大多是不變的。

應該考慮的另一種類型的分區是塊分區。這種類型的分區也被稱爲負載平衡算法,因爲工作線程在很多工作要做的時候很少會閒置 - 這不是範圍分區的情況。

當工作有一些等待狀態時,應該使用塊分區,每個元素往往需要更多的處理,或者每個元素可能有明顯不同的工作處理時間。

這樣做的一個例子可能是讀入內存並處理大小不同的100個文件。 1K文件的處理時間比1mb文件少得多。如果爲此使用範圍分區,則某些線程可能會閒置一段時間,因爲它們碰巧處理較小的文件。

與範圍分區不同,無法指定每個任務要處理的元素數 - 除非您編寫自己的定製分區程序。使用塊分區的另一個缺點是,當它返回到另一個塊時可能會有一些爭用,因爲在此時使用了排它鎖。所以,很顯然,一個塊分區不應該用於短時間的CPU密集型工作。

默認塊分區程序以每個塊的塊大小爲1個元素開始。在每個線程處理三個1元素塊之後,塊大小每塊增加到2個元素。每個線程處理了三個2元素塊後,塊大小再次遞增爲每個塊3個元素,依此類推。至少這是按照 Dixin Yan(參見塊分區部分)爲Microsoft工作的方式。

順便說一下,他博客中的可視化工具好像是Concurrency Visualizer profile tooldocs for this tool聲稱它可用於定位性能瓶頸,CPU利用率低下,線程爭用,跨核心線程遷移,同步延遲,DirectX活動,重疊I/O區域以及其他信息。它提供了圖形,表格和文本數據視圖,可以顯示應用程序中的線程與整個系統之間的關係。

其他資源:

MSDN: Custom Partitioners for PLINQ and TPL

Part 5: Parallel Programming - Optimizing PLINQ約瑟夫阿爾巴哈利