和の大きな配列のすべての要素:私たちは簡単なプログラミングの課題を考えてみましょう。簡単特に要素の数千または数百万の巨大な配列のために、それを最適化するために並列処理を使用することにより、並列処理コアとCPU時間だけ、従来の時間で除算されるべきであると信じる理由があります。これは、この偉業を達成することは容易ではないことが判明します。私は、彼らが改善したり、何らかの方法でパフォーマンスに影響を与えるすべての詳細だけでなく、パフォーマンスを低下させる方法を、この並列を行うには、あなたにいくつかの方法を紹介します。
単純なループ方式
民間 のconst int型 ITEMS = 500000 ; プライベート int型 [] ARR = nullを。 パブリックArrayC() { ARR = 新しい INT [ITEMS]。 VaRの RND = 新しいランダム(); 以下のために(int型私= 0 ; iがアイテムを<; Iは++ ) { [i]はARR = rnd.Next(1000年)。 } } パブリック ロングForLocalArr() { 長い合計= 0 ; にとって(int型 I = 0 ; iがアイテムを<; Iは++ ) { 合計 + = INT .Parse(ARR [I] .ToString())。 } 戻り総。 } パブリック ロングForeachLocalArr() { 長い合計= 0 ; foreachの(VARの項目でARR) { 合計 + = INT .Parse(item.ToString())。 } 戻り総。 }
ループの唯一の繰り返しが得られた結果のない直接の合計がない、超シンプル、結果を計算することができ、その理由は、取得した直接の結果であり、そのたびに、基本的な実行速度並行より見つけましたが、実際には、並列処理はそれほど単純ではありませんので、ここでは単に合計+ = int.Parse(ARR [I] .ToString())で加算処理です。さて、それの並列反復の配列を倒してみましょう。
最初の試み
プライベート オブジェクト _lock = 新しい オブジェクト(); 公衆 ロングThreadPoolWithLock() { 長い合計= 0 ; int型のスレッド= 8 ; VaRの partSize = ITEMS / スレッド。 タスク[]タスク = 新しいタスク[スレッド]。 用(INT ithreadの= 0 ; ithreadの<スレッドithreadの++ ) { VAR localThread = ithreadの。 タスク[localThread] = Task.Run(()=> { 用(int型 J = localThread * partSize。J <(localThread + 1)* partSize。J ++ ) { ロック(_lock) { 合計 + = ARR [J]。 } } })。 } Task.WaitAll(タスク)。 戻る合計; }
あなたは、時間のithreadの点の値を「保存」するlocalThread変数を使用しなければならないことに注意してください。そうでなければ、それは進歩と変更キャプチャー変数を持つループのためです。パラレルデータの最後のプレーが平均よりも速くなってますが、はるかに高速に発見したときは、命令はまた、最適化することができ、
再び最適化
公衆 ロングThreadPoolWithLock2() { 長い合計= 0 ; int型のスレッド= 8 ; VaRの partSize = ITEMS / スレッド。 タスク[]タスク = 新しいタスク[スレッド]。 用(INT ithreadの= 0 ; ithreadの<スレッドithreadの++ ) { VAR localThread = ithreadの。 タスク[localThread] = Task.Run(()=> { 長い TEMP = 0 ; のための(INTJ = localThread * partSize。J <(localThread + 1)* partSize。J ++ ) { TEMP + = INT .Parse(ARR [J] .ToString())。 } ロック(_lock) { 合計 + = TEMP。 } })。 } Task.WaitAll(タスク)。 戻る合計; }
ロックの数を減らし、一時変数の提供を増やし、業績は品質を向上していることを発見し、数倍向上。突然Parallel.For、研究業績がより速くなることができます方法があるかどうか、思い出しました。
Parallel.Forの最適化
公衆 ロングParallelForWithLock() { 長い合計= 0 ; int型部品= 8 ; int型 partSize = ITEMS / 部品。 VaRのパラレル= Parallel.For(0、部品、新しい ParallelOptions()、(ITER)=> { 長い TEMP = 0 ; のための(INT J = ITER * partSize; J <(ITER + 1)* partSizeあり、j ++ ) { TEMP + = INT .Parse(ARR [J] .ToString()); } ロック(_lock) { 合計 + = TEMPと、 } })。 戻る合計; }
業績は速く、通常の反復よりも、ないのThreadPool速いだけでなく、より速く、多分、Parallel.Forが考える最適化し続けることができます
Parallel.Forは、最適化し続けます
公衆 ロングParallelForWithLock2() { 長い合計= 0 ; int型部品= 8 ; int型 partSize = ITEMS / 部品。 VaRの平行= Parallel.For(0 、部品、 localInit:() => 0L、//は"localTotal"初期化 (ITER、状態、localTotal)=>:本体 { ため(INT J = ITER * partSize; jは<( ITER + 1)* partSizeあり、j ++ ) { localTotal + =INT .Parse(ARR [J] .ToString())。 } 戻りlocalTotal。 }、 localFinally:(localTotal) => {合計+ = localTotal。}); 戻る合計; }
営業結果は、時には速く、非常に迅速にして、ほとんどのThreadPoolに最適化されています
結論と要約
並列化の最適化は確かにパフォーマンスを向上させることができますが、それは多くの要因に依存し、それぞれの場合には、測定してチェックする必要があります。
ときに、いくつかのスレッドロックによって必要とされる様々な機構の相互依存性は、パフォーマンスが大幅に低減されます。