5

非常に大きなデータセットに対して非常にタイトなループを実行するメソッドがアルゴリズムにあります。もともとはシングルスレッドで大丈夫でしたが、時間がかかりました。私は今、それをスピードアップしたいと思っているところまで来ているので、今はThreadPoolを使用して作業を並列化しています。問題は、これによりCPU使用率が95〜100%になることです。これは、私が予想していたことです。ただし、パフォーマンスは劇的に向上しましたが、すべてのコンテキスト切り替えを削減できれば、パフォーマンスを向上させることができると思います。これにより、他のプログラムはCPUリソースのためにスレッドと戦わなければならないため、少し遅れます。

私の質問は、これをどのように行うべきかということです。私が考えることができたのは、一度に実行できるスレッドの数を制限することだけですが、一度に実行できるスレッドは数個しかないため、アルゴリズムが遅くなる可能性があります。アルゴリズムをできるだけ早く実行して完了する必要があるため、スレッドにスリープを追加したくありません。

編集:何人かの人々がTPLの使用について言及しました。それは素晴らしいアイデアだと思いますが、残念ながら、親アプリケーションが.NET 4を使用するバージョンをまだリリースしていないため、.NET3.5を使用することに固執していることを忘れました。

4

2 に答える 2

6

これはすべてリソース管理に関するものです。あなたのプログラムは現在すべてのリソースを占有しているため、他のプログラムはそれらへのアクセスが制限されます。「アルゴリズムをできるだけ早く完了する必要がある」と「これにより、他のプログラムもCPUリソースのスレッドと戦わなければならないため、少し遅れる」という部分のバランスをとる必要があります。それらは相互に排他的です。特定のマシンで可能な限り高速にアプリを実行したり、他のアプリの応答性を完全に維持したりすることはできません。CPUが任意の時間で実行できる量には制限があります。

効率が上がる限り、できることがいくつかあります。

  • 超最適化されたスレッドアルゴリズムにはThreadPoolを使用しないでください。ThreadPoolは、単純な「これを実行して、完了したことを通知する」操作に最適です。ただし、最適化を検討している場合は、ThreadPoolを使用してスレッドスケジューリングのレベルを追加することに伴うオーバーヘッド(CPUおよびOSに固有のオーバーヘッドに加えて)を回避できます。また、ThreadPool内のスレッドに対する制御が制限されます。つまり、個々のスレッドのプロセッサアフィニティ(負荷分散)や優先度(スレッドに多少の時間を与える)の割り当てなどの最適化は利用できません。単純なスレッドを作成するか、複数のことを実行するためのいくつかの戦略があるTPLを調べてみてください(すべてが最初にスレッド化を必要とするわけではありません)。

  • はい、スレッドの数を「スロットル」できるようにする必要があります。これは、プログラムの必要性を減らすことで他のプログラムにCPU時間を与えるためですが、前述したように、マルチスレッドに固有のオーバーヘッドもあります。概観では、CPUに「実行ユニット」(これらはCPUチップ上の物理コア、および1つのコアを分割するハイパースレッディングテクノロジーのような「論理プロセッサ」)があるため、アクティブに実行されているスレッドの数が2倍以上になる場合です。 2つに分割すると、OSは、実際にスレッドを実行するよりも、スレッドのスケジューリングとスレッド間の切り替え(「キャッシュスラッシング」)に多くの時間を費やします。より一般的に言えば、収穫逓減の法則があり、それは「規模の不経済」に発展します。最終的、別のスレッドを追加すると、そのスレッドを使用しなかった場合よりもプログラムの実行が遅くなります。はい、ThreadPoolは最大のスレッドを処理しますが、それはおそらく、独自のアルゴリズムで自分自身を実装するためのさまざまな機能の中で最も単純です。

  • 各スレッドの作業が最適化されていることを確認してください。ナイーブまたは非効率的なアルゴリズム(私はそれらを「O(私の神)-複雑さ」と呼んでいます)を探し、それらを合理化します。ほとんどの操作の効率には下限があり(操作の種類によって異なります)、「時期尚早の最適化はすべての悪の根源です」(コードを実際に機能させることを犠牲にしてパフォーマンスを最適化しないでください)。マルチスレッド環境では、アルゴリズムを1回実行したときに効率が上がると、実行回数が増えるため、並列操作が効率的であることを確認することは2つの利点です。

于 2012-04-13T15:15:46.783 に答える
2

メインアプリケーションをforeachループに書き換えることができる場合は、 PLINQIEnumerableを使用してループを並列化できます。WithDegreeOfParallelismを使用して、アプリケーションが使用するコアの数を制御できます。コンピューターのすべてのコアを使用しないことで、発生する「遅延」の一部を防ぐことができます。また、不必要なリソースの競合を回避するために、スレッド間でループを分割する方法に対処する必要はありません。PLINQはあなたのためにそれをすべて行います。

この非常に単純なシングルスレッドループがあると仮定します。

var arrayOfStuff = new[] { ... };
for (var i = 0; i < arrayOfStuff.Length; ++i)
  DoSomething(arrayOfStuff[i]);

順序が重要でない場合は、利用可能なコアより1つ少ないコアを使用して、PLINQを使用して並列化できます。

var cores = Math.Max(1, Environment.ProcessorCount - 1);
arrayOfStuff.AsParallel().WithDegreeOfParallelism(cores).ForAll(DoSomething);

メインループがより複雑な場合でも、それをイテレータブロックに書き換えて、並列化することができます。

IEnumerable<Stuff> GetStuff() {
  for ( ... very complex looping ... ) {
    ...
    yield return stuff;
  }
}
于 2012-04-13T15:15:01.297 に答える