パフォーマンスを向上させるためにマルチスレッドがしばしば好まれているのはなぜですか?


23

私は質問があります、それはプログラマーが一般的に並行性とマルチスレッドプログラムを好むように思われる理由についてです。

ここで2つの主なアプローチを検討しています。

  • 基本的に信号に基づく非同期アプローチ、または新しいC#5.0などの多くの論文や言語で呼ばれる非同期アプローチ、およびパイプラインのポリシーを管理する「コンパニオンスレッド」
  • 並行アプローチまたはマルチスレッドアプローチ

私はここでハードウェアと最悪のシナリオについて考えていると言いますが、この2つのパラダイムを自分でテストしました。物事をスピードアップしたり、リソースを有効に活用したい場合は、マルチスレッドについて話します。

CPU内にメモリコントローラーを提供しないIntelクアッドコアを備えた古いマシンでマルチスレッドプログラムと非同期プログラムをテストしました。メモリはマザーボードによって完全に管理されています。マルチスレッドアプリケーションでは、3-4-5のような比較的少数のスレッドでさえ問題になる可能性があり、アプリケーションは応答せず、遅くて不快です。

一方、優れた非同期アプローチはおそらく高速ではないかもしれませんが、最悪ではありません。私のアプリケーションは結果を待つだけでハングせず、応答性があり、はるかに優れたスケーリングが行われています。

また、スレッドの世界でのコンテキストの変更は、実際のシナリオではそれほど安くないことを発見しました。実際には、計算するために相互に循環してスワップする必要のあるスレッドが2つ以上ある場合、実際には非常に高価です。

現代のCPUでは、状況はそれほど違いはありませんが、統合されたメモリコントローラですが、私のポイントは、x86 CPUは基本的にシリアルマシンであり、メモリコントローラはマザーボード上の外部メモリコントローラを備えた古いマシンと同じように動作することです。コンテキストスイッチは依然としてアプリケーションに関連するコストであり、メモリコントローラーが統合されているか、新しいCPUに2コア以上あるという事実は私にとってはお買い得ではありません。

私が経験したコンカレントアプローチは理論的には良いが、実際にはそれほど良くありません。ハードウェアによってメモリモデルが課されているため、このパラダイムをうまく利用することは難しく、使用からデータ構造を複数のスレッドの結合に使用します。

また、両方のパラダイムは、特定の時点でタスクまたはジョブが実行されるときにセキュリティを提供しません。機能的な観点からは、それらは本当に似ています。

X86メモリモデルによると、なぜ大多数の人々は、非同期アプローチではなく、C ++で同時実行性を使用することを提案するのでしょうか。また、コンピュータの最悪のシナリオを考えてみてください。コンテキストスイッチは、おそらく計算自体よりも高価です。


2
比較する1つの方法は、コールバックを使用してスレッド化がなく、すべてが積極的に非同期であるJavaScriptの世界を調べることです。動作しますが、独自の問題があります。
ロボット

2
@StevenBurnapあなたはウェブワーカーと呼んでいますか?
user16764

2
「3-4-5のような比較的少数のスレッドでさえ問題になる可能性があり、アプリケーションは応答せず、ただ遅くて不快です。」=>設計の不備/スレッドの不適切な使用が原因である可能性があります。通常、このような状況は、スレッドがデータを交換し続けるときに発生します。この場合、マルチスレッド化は正しい答えではないか、データの再パーティション化が必要になる場合があります。
assylias

1
@assylias UIスレッドの大幅なスローダウンを確認するには、スレッド間でロックが過剰に行われていることを示します。実装が悪いか、丸い穴に四角い釘を打ち込もうとしています。
エヴァンプライス

5
「プログラマーは、並行性とマルチスレッドプログラム全般を好むようです」とあなたは言います。私は「プログラマはそれを嫌う」と言うだろう...しかし、多くの場合、それを行うための唯一の役に立つことだ...
ヨハネス

回答:


34

複数のコア/プロセッサがあり、それらを使用します

非同期、大量のIOバウンド処理を行うのに最適ですが、重いCPUバウンド処理はどうですか?

この問題は、長時間実行されているプロセスでシングルスレッドコードブロック(つまり、スタック)が発生したときに発生します。たとえば、ワードプロセッサドキュメントを印刷すると、ジョブが送信されるまでアプリケーション全体がフリーズすることを思い出してください。アプリケーションのフリーズは、CPUを集中的に使用するタスク中のシングルスレッドアプリケーションのブロッキングの副作用です。

マルチスレッドアプリケーションでは、CPUを集中的に使用するタスク(印刷ジョブなど)をバックグラウンドワーカースレッドに送信して、UIスレッドを解放できます。

同様に、マルチプロセスアプリケーションでは、ジョブをメッセージング(IPC、ソケットなど)を介して、ジョブを処理するために特別に設計されたサブプロセスに送信できます。

実際には、非同期およびマルチスレッド/プロセスコードにはそれぞれ利点と欠点があります。

主要なクラウドプラットフォームでは、CPUバウンド処理に特化したインスタンスとIOバウンド処理に特化したインスタンスが提供されるため、傾向を見ることができます。

例:

  • ストレージ(例Amazon S3、Google Cloud Drive)はCPUバウンドです
  • WebサーバーはIOバウンド(Amazon EC2、Google App Engine)
  • データベースは両方とも、書き込み/インデックス作成のCPUバウンドと読み取りのIOバウンドです

視点に入れるには...

Webサーバーは、IOに強くバインドされているプラ​​ットフォームの完璧な例です。接続ごとに1つのスレッドを割り当てるマルチスレッドWebサーバーは、共有リソースのコンテキストスイッチングとスレッドロックの量が増加するため、すべてのスレッドのオーバーヘッドが増加するため、スケーリングがうまくいきません。一方、非同期Webサーバーは単一のアドレススペースを使用します。

同様に、ビデオのエンコードに特化したアプリケーションは、処理が完了するまでメインスレッドをロックするため、大量の処理が行われるため、マルチスレッド環境でより適切に動作します。これを軽減する方法はありますが、キューを管理する単一のスレッド、クリーンアップを管理する2番目のスレッド、および重い処理を管理するスレッドのプールを持つ方がはるかに簡単です。スレッド間の通信は、タスクの割り当て/完了時にのみ発生するため、スレッドロックのオーバーヘッドは最小限に抑えられます。

多くの場合、最適なアプリケーションは両方の組み合わせを使用します。たとえば、webappは、nginx(つまり、非同期シングルスレッド)をロードバランサーとして使用して、着信リクエストのトレント、httpリクエストを処理する同様の非同期ウェブサーバー(例:Node.js)、およびマルチスレッドサーバーのセットを管理できます。コンテンツのアップロード/ストリーミング/エンコードなどを処理します...

マルチスレッドモデル、マルチプロセスモデル、および非同期モデルの間で、長年にわたって多くの宗教的な戦争がありました。ほとんどの場合と同様に、最良の答えは「依存する」です。

GPUとCPUアーキテクチャを並行して使用することを正当化する同じ考え方に従っています。連携して実行される2つの特殊なシステムは、単一のモノリシックアプローチよりも大幅に改善できます。

どちらにも用途があるため、どちらも優れていません。仕事に最適なツールを使用してください。

更新:

Apacheへの参照を削除し、少し修正しました。Apacheは、リクエストごとにプロセスを分岐するマルチプロセスモデルを使用して、カーネルレベルでのコンテキストスイッチングの量を増やします。さらに、プロセス間でメモリを共有できないため、各リクエストには追加のメモリコストがかかります。

マルチスレッドは、スレッド間の共有メモリに依存するため、追加のメモリを必要とします。共有メモリは、追加のメモリオーバーヘッドを取り除きますが、コンテキストスイッチングの増加というペナルティを負います。さらに、競合状態が発生しないようにするため、スレッド間で共有されるリソースにはスレッドロック(一度に1つのスレッドのみへの排他的アクセスを保証する)が必要です。

「プログラマーは、並行性とマルチスレッドプログラム全般を好むようです」と言うのは面白いことです。マルチスレッドプログラミングは、その時代にかなりの量を行ったことのある人によって普遍的に恐れられています。デッドロック(2つの異なるソースによってリソースが誤ってロックされ、フィニッシュの両方がブロックされる場合に発生するバグ)および競合状態(プログラムが誤ったシーケンスのために誤って誤った結果をランダムに出力する場合)は、追跡が最も難しいものです。ダウンして修正します。

Update2:

IPCがネットワーク(ソケット)通信よりも高速であるという包括的な声明に反して。それは常にそうではありません。これらは一般化であり、実装固有の詳細が結果に大きな影響を与える可能性があることに留意してください。


なぜプログラマーはマルチプロセスに移行する必要があるのですか?複数のプロセスでは、かなりのオーバーヘッドを追加する何らかのプロセス間通信も必要だと思いますが、これは古いWindowsプログラマーのやり方のようなものですか?いつマルチプロセスに行くべきですか?ところで、お返事をありがとう、非同期とマルチスレッドが何のために良いのかの本当に良い絵。
user1849534

1
プロセス間通信により全体的なオーバーヘッドが増加すると想定しています。ただし、処理状態が不変である場合、または開始/完了時に同期を処理するだけでよい場合。より多くの並列タスクにファンアウトする方がはるかに効率的です。俳優のパターンは良い例であり、もしあなたがそれについて読んでいないなら、それは本当に読む価値があります。akka.io
sylvanaar

1
@ user1849534複数のスレッドは、共有メモリ+ロックまたは IPCを介して互いに通信できます。ロックは簡単ですが、間違えた場合(デバッグの失敗、デッドロックなど)のデバッグが難しくなります。IPCは、ロックが適切にスケーリングされないため、多くのワーカースレッドがある場合に最適です。いずれにせよ、マルチスレッドのアプローチを使用している場合、スレッド間の通信/同期を絶対最小限に抑える(つまり、オーバーヘッドを最小限に抑える)ことが重要です。
エヴァンプライス

1
@ akka.ioまったくその通りです。不変性は、ロックのオーバーヘッドを最小化/排除するための1つの方法ですが、コンテキストスイッチングの時間コストが発生します。不変性がスレッド同期の問題を解決する方法に関する詳細を含むように答えを拡張したい場合は、お気軽に。私が説明しようとした主なポイントは、非同期通信がマルチスレッド/プロセスに対して明確な利点を持っている場合と、その逆の場合があるということです。
エヴァンプライス

(続き)しかし、正直なところ、CPUにバインドされた多くの処理機能が必要な場合は、アクターモデルをスキップして、複数のネットワークノードにスケーリングできるように構築しました。これについて私が見た最良の解決策は、ソケットレベルの通信で0MQのタスクベンチレーターモデルを使用することです。図5 @ zguide.zeromq.org/page:allを参照してください。
エヴァンプライス

13

Microsoftの非同期アプローチは、マルチスレッドプログラミングの最も一般的な目的(IOタスクに対する応答性の向上)に代わるものです。

ただし、非同期アプローチでは、パフォーマンスをまったく向上させることも、CPUを集中的に使用するタスクに対する応答性を向上させることもできないことを認識することが重要です。

応答性のためのマルチスレッド

応答性のためのマルチスレッドは、重いIOタスクまたは重い計算タスク中にプログラムの応答性を維持する従来の方法です。バックグラウンドスレッドにファイルを保存すると、ユーザーはハードドライブがタスクを完了するのを待たずに作業を続行できます。IOスレッドは多くの場合、書き込みの一部が終了するのを待機してブロックするため、コンテキストの切り替えが頻繁に発生します。

同様に、複雑な計算を実行する場合、UIの応答性を維持し、ユーザーがプログラムがクラッシュしたとは思わないように、通常のコンテキストスイッチングを許可する必要があります。

ここでの目標は、一般に、複数のスレッドを異なるCPUで実行することではありません。代わりに、バックグラウンドタスクの実行中にUIが更新されてユーザーに応答できるように、長時間実行されるバックグラウンドタスクとUIの間でコンテキストスイッチが発生することに関心があります。一般的に、UIは多くのCPUパワーを消費せず、スレッドフレームワークまたはOSは通常、それらを同じCPUで実行することを決定します。

コンテキストの切り替えに余分なコストがかかるため、実際には全体的なパフォーマンスが低下しますが、CPUのパフォーマンスが目標ではなかったため、気にしません。通常、必要以上のCPUパワーを持っていることがわかっているため、マルチスレッドに関する目標は、ユーザーの時間を無駄にすることなく、ユーザーのタスクを完了させることです。

「非同期」の代替

「非同期アプローチ」は、単一のスレッド内でコンテキストスイッチを有効にすることにより、この状況を変化させます。これにより、すべてのタスクが単一のCPUで実行されることが保証され、スレッドの作成/クリーンアップが少なくなり、スレッド間の実際のコンテキスト切り替えが少なくなるという点で、パフォーマンスが若干向上します。

ネットワークリソースの受信を待機する新しいスレッドを作成する代わりに(イメージのダウンロードなど)、asyncメソッドが使用されます。これawaitにより、イメージが利用可能になり、その間、呼び出し元のメソッドに譲ります。

ここでの主な利点は、ロックと同期をまったく使用していないため、デッドロックを回避するなどのスレッドの問題を心配する必要がなく、プログラマーがバックグラウンドスレッドを設定して戻ってくる作業が少し少ないことです。 UIを安全に更新するために、結果が戻ったときにUIスレッドで。

技術的な詳細をあまり深く見ていませんが、時々のCPUアクティビティが少ないダウンロードを管理することは、個別のスレッドではなく、UIイベントキューのタスクのようなものになり、ダウンロードが完了すると、非同期メソッドがそのイベントキューから再開されます。つまり、await「必要な結果が利用可能かどうかを確認し、利用できない場合は、このスレッドのタスクキューに戻してください」という意味です。

このアプローチはCPU集中型タスクの問題を解決しないことに注意してください。待機するデータがないため、実際のバックグラウンドワーカースレッドを作成せずにコンテキストスイッチを取得することはできません。もちろん、非同期アプローチを広く使用しているプログラムでは、非同期メソッドを使用してバックグラウンドスレッドを開始し、結果を返すと便利な場合があります。

パフォーマンスのためのマルチスレッド

「パフォーマンス」について説明しているので、パフォーマンス向上のためにマルチスレッドを使用する方法についても説明したいと思います。これは、シングルスレッドの非同期アプローチではまったく不可能なことです。

実際に、単一のCPUで十分なCPU能力を持っていない状況にあり、パフォーマンスのためにマルチスレッドを使用したい場合、実際に実行するのは難しいことがよくあります。一方、1つのCPUで十分な処理能力がない場合、それは多くの場合、プログラムが合理的な時間枠で達成したいことを実行できるようにする唯一のソリューションであり、これが作業を価値のあるものにします。

自明な並列処理

もちろん、場合によって、マルチスレッドから実際のスピードアップを簡単に得ることができます。

多数の独立した計算集約タスク(つまり、結果を決定するために実行する必要がある計算に関して入力データと出力データが非常に小さいタスク)が発生した場合、多くの場合、大幅な高速化を実現できます。スレッドのプール(使用可能なCPUの数に基づいて適切なサイズ)を作成し、マスタースレッドに作業を分散させて結果を収集します。

パフォーマンスのための実用的なマルチスレッド

私はあまり専門家になりたくはありませんが、私の印象では、一般的に、最近行われているパフォーマンスのための最も実用的なマルチスレッドは、些細な並列性を持つアプリケーション内の場所を探し、複数のスレッドを使用することです利益を享受するために。

最適化と同様に、通常はプログラムのパフォーマンスをプロファイルし、ホットスポットを特定した後に最適化することをお勧めします。この部分をあるスレッドで実行し、その部分を別のスレッドで実行することを任意に決定することで、プログラムをスローダウンするのは簡単です最初に、両方の部分がCPU時間のかなりの部分を占めているかどうかを判断します。

スレッドが増えると、セットアップ/ティアダウンのコストが増加し、コンテキストの切り替えまたはCPU間の通信コストが増加します。別個のCPU上にある場合、それらのコストを補うのに十分な作業を行っておらず、応答性の理由で別個のスレッドである必要がない場合、速度は低下します。

相互依存性がほとんどなく、プログラムのランタイムの大部分を占めているタスクを探します。

それらに相互依存性がない場合、それは些細な並列性の場合であり、スレッドで簡単にセットアップして利点を享受できます。

相互依存性が制限されているタスクを見つけることができ、情報を交換するためのロックと同期によってタスクの速度が大幅に低下しない場合、同期時または誤ったロジックによるデッドロックの危険を回避するために注意を払うと、マルチスレッドによってある程度の速度向上が得られます必要なときに同期しないため、誤った結果になります。

あるいは、マルチスレッドのより一般的なアプリケーションのいくつかは、(ある意味では)所定のアルゴリズムの高速化を求めていませんが、代わりに、彼らが書く予定のアルゴリズムのより大きな予算を求めています:ゲームエンジンを書いている場合、AIはフレームレート内で決定する必要があるため、独自のCPUを使用できる場合は、AIにより多くのCPUサイクルバジェットを割り当てることができます。

ただし、スレッドのプロファイルを作成し、ある時点でコストを補うのに十分な作業を行っていることを確認してください。

並列アルゴリズム

また、複数のプロセッサを使用して高速化できる多くの問題がありますが、それらは単純すぎてCPU間で分割できません。

並列アルゴリズムは、複数のCPUを使用することによるメリットを排除するためにCPU間通信コストが非常に簡単であるため、利用可能な最良の非並列アルゴリズムに関して、big-Oランタイムについて慎重に分析する必要があります。一般に、各CPUで計算を使用するよりも、CPU間通信(big-O用語)を少なくする必要があります。

現時点では、複雑な分析が必要なため、一部には些細な並列処理が非常に一般的であるため、一部にはコンピューターにあまり多くのCPUコアがないため、学術研究のためのスペースです1つのCPUで妥当な時間枠で解決できない場合は、すべてのCPUを使用して妥当な時間枠で解決できます。


明らかによく考えられた答えに対して+1。ただし、Microsoftの提案を額面通りに受け取ることには注意を払います。.NETは同期優先プラットフォームであるため、同期ソリューションの構築をサポートするより優れた施設/ドキュメントを提供することにエコシステムが偏っていることに留意してください。Node.jsのような非同期優先プラットフォームの場合、逆のことが当てはまります。
エヴァンプレイス

3

アプリケーションが応答せず、遅くて不快です。

そして、あなたの問題があります。レスポンシブUIは、パフォーマンスの高いアプリケーションを作成しません。しばしば反対。ワーカースレッドにジョブを実行させるのではなく、UI入力のチェックに多くの時間が費やされます。

「ちょうど」非同期アプローチを持っている限り、それはほとんどの環境でその特定のユースケースのために微調整されましたが、マルチスレッドでもあります。他では、その非同期はコルーチンを介して行われます...常に同時ではありません。

率直に言って、非同期操作は、より多くの手動のアプローチと比較しても、実際に利点(パフォーマンス、堅牢性、保守性)を提供する方法で推論および使用するのが難しいことがわかります。


どうして ?たとえば、boostssignalsライブラリでバナナを見つけるのは何ですか?
user1849534
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.