web-dev-qa-db-ja.com

パフォーマンスを改善するためにマルチスレッドがしばしば好まれるのはなぜですか?

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

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

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

ここでハードウェアと最悪のシナリオについて考えていると言うだけで、私はこの2つのパラダイムを自分でテストしました。非同期パラダイムは、人々が90%の確率で理由を理解できないという点で勝者です物事を高速化したり、リソースを有効に利用したい場合は、マルチスレッドについて話します。

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

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

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

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

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

また、どちらのパラダイムも、タスクまたはジョブが特定の時点で実行される場合、セキュリティ面でのサポートを提供しません。これにより、機能の観点から見れば、それらは非常に類似しています。

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

23
user1849534

複数のコア/プロセッサがありますseそれら

非同期is重い処理を行うのに最適IOバインドされた処理ですが、重いCPUバインドされた処理はどうですか?

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

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

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

実際には、非同期andマルチスレッド/プロセスコードにはそれぞれ長所と短所があります。

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

例:

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

それを遠近法に当てはめる...

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

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

最良のアプリケーションは、多くの場合、両方の組み合わせを使用します。たとえば、webappは、nginx(つまり、非同期シングルスレッド)をロードバランサーとして使用して、着信要求の急流を管理し、同様の非同期webserver(ex。Node.js)を使用してhttp要求を処理し、マルチスレッドサーバーのセットを使用します。コンテンツのアップロード/ストリーミング/エンコードなどを処理します...

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

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

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

更新:

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

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

おもしろいのは、「プログラマーは並行性とマルチスレッド化されたプログラムを一般的に愛しているようだ」ということです。マルチスレッドプログラミングは、その時間の間にかなりの量を実行したことのある人によって、一般的に恐れられています。 Dead locks (リソースが2つの異なるソースによって誤ってロックされ、両方が常に終了しないようにブロックする場合に発生するバグ)および 競合状態 (プログラムが誤った結果を誤って出力する場所)シーケンスが正しくないためにランダムに発生します)は、追跡して修正するのが最も難しいものです。

Update2:

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

34
Evan Plaice

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

ただし、非同期のアプローチでは、パフォーマンスをまったく向上させたり、CPUを多用するタスクに関する応答性を向上させたりできないことを理解することが重要です。

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

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

同様に、複雑な計算を実行するときは、定期的なコンテキスト切り替えを許可して、UIが応答性を維持できるようにし、ユーザーがプログラムがクラッシュしたとは思わないようにします。

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

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

「非同期」の代替

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

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

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

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

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

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

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

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

単純な並列処理

もちろん、マルチスレッドから実際のスピードアップを簡単にできる場合もあります.

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

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

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

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

追加のスレッドは、より多くのセットアップ/ティアダウンコストと、より多くのコンテキストスイッチまたはより多くのCPU間通信コストを意味します。別のCPUでこれらのコストを補うのに十分な作業を行っておらず、応答性の理由で別のスレッドである必要がない場合は、速度が低下してメリットがありません。

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

それらに相互依存性がない場合、それは些細な並列処理の場合であり、スレッドを使用してそれぞれを簡単にセットアップし、その利点を楽しむことができます。

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

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

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

並列アルゴリズム

複数のプロセッサを使用してスピードアップできる問題もたくさんありますが、それらはモノリシックすぎて単純にCPU間で分割できません。

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

現時点では、一部には複雑な分析が必要であること、一部には単純な並列処理が非常に一般的であること、一部にはコンピュータにまだ多くのCPUコアがないために問題が発生することなどが原因で、依然として学術研究用のスペースが大部分です1つのCPUで妥当な時間枠で解決できない場合は、すべてのCPUを使用して妥当な時間枠で解決できます。

13

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

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

非同期のアプローチを持っている「だけ」に関する限り、それはマルチスレッドでもありますが、特定の1つの使用例ほとんどの環境に合わせて調整されています。他の人では、非同期はコルーチンを介して行われます...常に同時ではありません。

率直に言って、私は非同期操作を推論し、実際に利点(パフォーマンス、堅牢性、保守性)を提供する方法で使用することは、手動操作よりも難しいと感じています。

3
Telastyn