[PLINQ]輸入優化

區塊分割(chunk partition)、定界分割(range partition)

若要將對資料來源執行的作業平行化,其中一個必要步驟是將來源「分割」(Partition) 成多個可供多個執行緒並行存取的區段。

分割資料來源的方式有許多種,最有效的方式是讓多個執行緒合作處理原始來源序列,而不是將來源實際分割成多個子序列。

 

PLINQ最主要的兩種分割方式

區塊分割(Chunk Partition)

大部分情況PLINQ都會使用區塊分割,除了來源是 Array 或者 實作IList<T> 的資料。
每一個執行緒會定期從輸入元素抓取一小塊chunks,PLINQ會開始分配小塊chunks(一次一個或兩個元素),然後在查詢執行過程中,增加chunk 的大小。這是可以確保小塊元素有效率的被平行執行,而有大塊的執行緒不會執行過多的chunk。  且執行緒分配到chunk,會開始執行,如果有執行緒執完成時間比較早,則會取得更多的chunk,PLINQ會執行讓每一條執行緒同樣忙碌(負載平衡)。

那所謂「區塊分割」(Chunk partitioning) 的資料來源「大」的時候會啟用負載平衡,這裡的「大」是甚麼定義?

原文:
Chunk partitioning works by having each worker thread periodically grab small “chunks” of elements from the input sequence to process. PLINQ starts by allocating very small chunks (one or two elements at a time), then increases the chunk size as the query progresses: this ensures that small sequences are effectively parallelized and large sequences don’t cause excessive round-tripping. If a worker happens to get “easy” elements (that process quickly) it will end up getting more chunks. This system keeps every thread equally busy (and the cores “balanced”)
chunk來源,係指來源元素的大小,以下圖為例,我有a~f的6個字元的來源,且chunk size 為 1 表示每一個chunk會抓取一個來源元素,
第一步:chunk 取得 元素a ,Thread 1 拿到有元素a的chunk。
第二步:chunk 取得 元素b,Thread 2 拿到有元素b的chunk。
第三步:chunk 取得 元素c,Thread 1 拿到有元素c的chunk。
第四步:chunk 取得 元素d,並發現Thread 2 chunk比較大,所以Thread 1拿到有元素d的chunk。
第五步:chunk 取得 元素e,發現兩邊chunk平衡了,Thread 2 拿到有元素e的chunk。
第六步:chunk 取得 元素f,Thread 1 拿到有元素f的chunk。

"abcdef".AsParallel().Select(x => char.ToUpper(x))

 

定界分割(Range Partition)

當來源資料是有索引的,例如: Array 或者 實作IList<T> 的資料,則PLINQ會預設使用定界分割。
定界分割會預先分配同樣元素的數量到每一條執行緒上,避免爭奪輸入端的元素,當有執行緒預先完成任務後,則會空閒等待其他的執行緒持續完成作業。

定界分割只有在委派時間不長,來源具有大量項目,且每個分割的總工作量大致相同時較快。
因此在大多數情節中區塊分割比較快。 在來源具有小量項目或委派執行較長時,區塊分割和定界分割的效能幾乎相等。

所以當定界分割來源的項目分配不均時,可以使用Partitioner執行區塊分割,以下範例:

// Array 定界分割
var nums = Enumerable.Range(0, 10000000).ToArray();

// 開啟負載平衡,告訴PLINQ要使用區塊分割。
Partitioner<int> customPartitioner = Partitioner.Create(nums, true);

var q = (from x in customPartitioner.AsParallel()
            select x * Math.PI).ToArray();
反之,如果區塊分割要改成定界分割,只要ToArray或者ToList就可以了。

 

參考

http://www.albahari.com/threading/part5.aspx#_Optimizing_PLINQ

https://msdn.microsoft.com/zh-tw/library/dd997411(v=vs.110).aspx

 

 

一天一分享,身體好健康。

該追究的不是過去的原因,而是現在的目的。