並行計算無疑是.Net Framework平台的一大亮點,它自動的將一個任務分解,並以並發的形式執行,程式員不用操心各任務之間的協作和同步問題,這使得可以更加專註於業務的實現。
.NET 中的 TPL(Task Parallel Library),中文意思是工作平行程式庫,它的設計是為了能更簡單地編寫可自動使用多處理器的Managed 程式碼。使用該庫,使用者可以非常方便地用現有序列代碼錶達潛在並行性,這樣序列代碼中公開的並行任務將會在所有可用的處理器上同時運行,通常這會大大提高速度。
但是,從網上很多已經發布的並行計算的例子來講,有很多存在一定的誤區甚至是誤導,這導致了一線編程人員產生一些錯誤的思路,它們多是通過樣本講述並行計算的效能優越性,似乎程式人員可以不費吹灰之力就能將程式效能提升N倍,如果這些想法沒有經過比較就應用於實際,那麼就會造成一定的損失。這篇文章就來聊聊關於合理使用並行計算的問題,供大家參考,這些誤區主要包括:
1. 只要使用並行就會提高程式效能
2. 並行迴圈嵌套越多程式效能越高
3. 並行計算是運行時的事
下面讓我們來一個個的講解這些誤會。
● 誤區一 .只要使用並行就會提高程式效能
即時並不是這樣,實際上並行計算的使用對前提要求非常嚴格,一般情況大量使用並行計算不但不會提升效能,反而會適得其反,下面有兩個Case給大家說明。
Case 1. 使用Thread.Sleep()比較並行與單行程式的效能並不客觀。
在許多並行計算與單行方式程式效能比較的例子中,很多都包含類似Thread.Sleep()的語句,運行這樣的Demo我們確實看到,並行的時間結果竟然提升如此許多,但是你有沒有仔細研究一下時間降低的原因呢?
有如下兩段代碼:
Code Part A:
for (int i = 0; i < 10; i++)
{
a = i.ToString();
Thread.Sleep(200);
}
Code Part B:
Parallel.For(0, 10, (i) =>
{
a = i.ToString();
Thread.Sleep(200);
});
在我的雙核本機上,測試結果是令人興奮的:Code Part A跑了2秒多,而Code Part B只跑了800多毫秒,時間大幅降低,然而這樣你就決定將你的代碼遷移到並行方式嗎?
我建議你還是等等再說吧,Code Part B比A具有更高效能的原因不是因為主代碼並行而帶來的效能提升,而是由於Sleep(),在並行環境中,任務實際上只是休息了1/N(N為並行數量),而不是單行程式中的全部,這是因為TPL將迴圈工作分解的緣故,在雙核本機上,Code Part B相當於2個5次的迴圈同時進行,Sleep()又很少有共用資源的消耗,不需要與其他進程同步,所以已耗用時間比1次10次的迴圈降低了,假如我們去掉Code Part A、B中的Sleep語句,那麼結果又是如何呢?答案是兩者十分趨近。實際上在主代碼短短小的情況下,並行計算會表現出一定的效能不穩定性,這裡留一點,感興趣的朋友自己測試一下吧。
選擇並行計算處理問題,首先要保證你的樣本或需求適合并行處理,比如寫排序演算法時就不能使用並行計算。
Case 2: 並行程式對於字串與數位處理效率是不同的。
@ 字串累加:
單行:
for (int i = 0; i < 100000; i++)
{
a += i.ToString();
}
並行:
Parallel.For(0, 100000, (i) =>
{
a += i.ToString();
});
@ 字串比較:
單行:
for (int i = 0; i < 100000; i++)
{
f = a.Equals(i.ToString());
}
並行:
Parallel.For(0, 100000, (i) =>
{
f = a.Equals(i.ToString());
});
@ 數字比較:
單行:
for (int i = 0; i < 100000000; i++)
{
f = i == j;
}
並行:
Parallel.For(0, 100000000, (i) =>
{
f = i == j;
});
運行以上三段測試代碼可知,TPL對於字串處理還是很令人滿意的。所以不是所有情況都適合使用TPL庫處理常式,這一點對於程式中的遍曆情景很重要,在使用PLINQ時,建議先分析樣本空間的類型與具體操作,再決定使用哪種計算。
● 誤區二.並行迴圈嵌套越多程式效能越高
對於兩層以上迴圈的程式碼片段,在你決定使用並行前,先要做的是發現這段代碼的主要耗損在哪,是在外層還是在內層,只有評估當並行對效能帶來的提升大於損耗時,再重構為並行也不遲,因為TPL在很多情形都提供了並行支援,很多程式員在能用到並行的地方都用並行,而沒有經過測試比較,實際上有很多時候,在所有的地方加上並行特性,程式的效能反而會受到損失,這裡就不在給出案例了。
從可以看出,TPL雖然自動管理了迴圈中的對象,但是這些“自動”是有一些效能損失的,如果我們的代碼中不斷地要求TPL進行對象的拆分、合并、同步,而忽視了業務本身的最佳化,那無疑是對效能不利的。
在這裡Aicken給出一個建議,就是在並行前,先要評估這段代碼的任務量有多大,有沒有必要並行?這段代碼有沒有對磁碟等“寫”要求的競爭?代碼是處理什麼任務的,以及代碼的運行環境是否支援並行;再者就是代碼重構後進行效能測試,這樣才能保證並行計算有意義。
其實,用對並行計算除了對效能的提升外,還有一點可貴的地方,就是對代碼的重構,簡潔而富有結構性的代碼,更加符合編碼進步的要求,不是嗎?
(待續)