非ブロッキングI / Oは、マルチスレッドのブロッキングI / Oよりも本当に高速ですか?どうやって?


117

ブロックI / Oと非ブロックI / Oに関するいくつかの技術的な詳細でWebを検索したところ、非ブロックI / OはブロックI / Oよりも高速であると述べた人が何人か見つかりました。このドキュメントの例です。

ブロックI / Oを使用する場合、もちろん現在ブロックされているスレッドは他に何もできません...ブロックされているためです。ただし、スレッドがブロックされ始めるとすぐに、OSは別のスレッドに切り替えることができ、ブロックされたスレッドに対して何かするまで、元に戻せません。したがって、CPUを必要とし、ブロックされていない別のスレッドがシステムにある限り、イベントベースの非ブロックアプローチと比較して、CPUアイドル時間はこれ以上ないはずです。

CPUがアイドル状態である時間を減らすことに加えて、特定の時間枠でコンピューターが実行できるタスクの数を増やすためのもう1つのオプションがあります。スレッドの切り替えによって生じるオーバーヘッドを減らします。しかし、これをどのように行うことができますか?そして、オーバーヘッドは測定可能な効果を示すのに十分な大きさですか?これが私がそれをどのように機能させるかについてのアイデアです:

  1. ファイルの内容をロードするために、アプリケーションはこのタスクをイベントベースのI / Oフレームワークに委任し、ファイル名とともにコールバック関数を渡します
  2. イベントフレームワークはオペレーティングシステムに委任します。オペレーティングシステムは、ファイルをメモリに直接書き込むようにハードディスクのDMAコントローラをプログラムします。
  3. イベントフレームワークにより、さらにコードを実行できます。
  4. ディスクからメモリへのコピーが完了すると、DMAコントローラが割り込みを発生させます。
  5. オペレーティングシステムの割り込みハンドラは、ファイルがメモリに完全に読み込まれていることをイベントベースのI / Oフレームワークに通知します。それはどのように行うのですか?信号を使用していますか?
  6. イベントI / Oフレームワーク内で現在実行されているコードが終了します。
  7. イベントベースのI / Oフレームワークはキューをチェックし、ステップ5のオペレーティングシステムのメッセージを確認して、ステップ1で取得したコールバックを実行します。

それはどのように機能しますか?そうでない場合、どのように機能しますか?つまり、スタックを明示的に操作する必要なく、イベントシステムが機能できるということです(スレッドを切り替えながらスタックをバックアップし、別のスレッドのスタックをメモリにコピーする必要がある実際のスケジューラなど)。これで実際にどれだけの時間を節約できますか?もっとありますか?


5
短い答え:それは接続ごとにスレッドを持つオーバーヘッドの詳細です。非ブロッキングioを使用すると、接続ごとにスレッドが発生するのを回避できます。
Dan D.

10
接続が存在するのと同じ数のスレッドを作成できないシステムでは、ブロッキングIOは高価です。JVMでは数千のスレッドを作成できますが、100.000を超える接続がある場合はどうでしょうか。したがって、非同期ソリューションに固執する必要があります。ただし、Go / Erlang / Rustのようにスレッドが高価ではない言語(グリーンスレッドなど)があり、100.000のスレッドがあっても問題はありません。スレッドの数が多い場合、IOをブロックすると応答時間が速くなると思います。しかし、それが現実に当てはまるかどうかも専門家に尋ねる必要があります。
OlliP 2014年

@OliverPlowもそうだと思います。ブロックIOは通常、システムが「並列管理」を処理することを意味し、タスクキューなどを使用して自分で処理するのではありません。
パセリエ2014年

1
@DanD。、そして、スレッドを持つことのオーバーヘッドが非ブロッキングIOを実行するオーバーヘッドと等しい場合はどうなりますか?(通常、緑の糸の場合に当てはまります)
パチェリエ2014年

「スタックのコピー」は行われません。異なるスレッドは、異なるアドレスにスタックを持っています。各スレッドには、他のレジスタと共に、独自のスタックポインタがあります。コンテキストスイッチは、アーキテクチャー状態(すべてのレジスターを含む)だけを保存/復元しますが、メモリーは保存しません。同じプロセス内のスレッド間では、カーネルはページテーブルを変更する必要さえありません。
Peter Cordes

回答:


44

ノンブロッキングまたは非同期I / Oの最大の利点は、スレッドが処理を並行して継続できることです。もちろん、追加のスレッドを使用してこれを実現することもできます。全体的な(システム)パフォーマンスを最高にするために述べたように、複数のスレッドではなく非同期I / Oを使用する方がよい(つまり、スレッドの切り替えを減らす)と思います。

並列接続された1000クライアントを処理するネットワークサーバープログラムの可能な実装を見てみましょう。

  1. 接続ごとに1つのスレッド(ブロックI / Oの場合もありますが、非ブロックI / Oの場合もあります)。
    各スレッドにはメモリリソース(カーネルメモリも!)が必要ですが、これは欠点です。また、スレッドが1つ増えるごとに、スケジューラーの作業が増えます。
  2. すべての接続に対して1つのスレッド。
    スレッド数が少ないため、システムから負荷がかかります。ただし、1つのプロセッサを100%まで駆動し、他のすべてのプロセッサをアイドル状態にする可能性があるため、マシンのフルパフォーマンスを使用できなくなります。
  3. 各スレッドがいくつかの接続を処理するいくつかのスレッド。
    スレッド数が少ないため、システムの負荷がかかります。また、使用可能なすべてのプロセッサを使用できます。Windowsでは、このアプローチはスレッドプールAPIでサポートされています

もちろん、スレッドを増やすこと自体は問題ではありません。ご存じかもしれませんが、私はかなり多くの接続/スレッドを選択しました。ダーススレッドのみについて話している場合、3つの可能な実装の間に違いが見られるとは思いません(これは、レイモンドチェンがMSDNブログの投稿で提案していることです。Windowsにはプロセスあたり2000スレッドの制限がありますか?)。

Windowsでは、バッファリングされていないファイルI / Oを使用すると、書き込みはページサイズの倍数のサイズでなければなりません。私はテストしていませんが、これはバッファリングされた同期および非同期書き込みの書き込みパフォーマンスにプラスの影響を与える可能性があるようです。

説明するステップ1から7は、それがどのように機能するかについての良い考えを与えます。Windowsでは、オペレーティングシステムは、イベントまたはコールバックを使用WriteFileして、非同期I / O(OVERLAPPED構造付き)の完了について通知します。コールバック関数は、たとえば、コードがWaitForMultipleObjectsExbAlertable設定された状態で呼び出された場合にのみ呼び出されますtrue

ウェブでさらに読む:


Webの観点から、常識(インターネット、専門家からのコメント)は、最大数を大幅に増やすことを提案しています。リクエストスレッドの数は、メモリの増加とコンテキストの切り替え時間のためにIOをブロックする(リクエストの処理をさらに遅くする)には悪いことですが、非同期IOは、ジョブを別のスレッドに延期するときに同じことをしていませんか?はい、より多くのリクエストを処理できますが、バックグラウンドで同じ数のスレッドがあります。そのことの本当の利点は何ですか?
JavierJ 2015年

1
@JavierJあなたは、n個のスレッドが非同期ファイルIOを実行すると、ブロッキングファイルIOを実行するために別のn個のスレッドが作成されると信じているようです。本当じゃない。OSには非同期ファイルのIOサポートがあり、IOが完了するのを待つときにブロックする必要はありません。IO要求をキューに入れることができ、ハードウェア(DMAなど)の割り込みが発生した場合、要求に完了のマークを付け、呼び出し元のスレッドに信号を送るイベントを設定できます。追加のスレッドが必要な場合でも、OSは複数のスレッドからの複数のIO要求にそのスレッドを使用できます。
Werner Henze 2015年

おかげで、OS非同期ファイルのIOサポートに関係するのは理にかなっていますが、これを(Webの観点から)実際に実装するためのコードを記述したときに、Java Servlet 3.0 NIOで、リクエストのスレッドとバックグラウンドスレッドがまだ表示されています( async)ループしてファイル、データベースなどを読み取ります。
JavierJ 2015年

1
@piyushGoyal私は私の答えを書き直しました。私はそれが今より明確であることを望みます。
Werner Henze

1
非同期ファイルI / Oを使用するWindowsでは、書き込みはページサイズの倍数のサイズでなければなりません。-いいえ、そうではありません。バッファリングされていないI / Oを考えています。(これらはしばしば一緒に使用されますが、必ずしもそうである必要はありません。)
ハリー・ジョンストン

29

I / Oには、ハードドライブからのデータの読み取りや書き込み、ネットワークリソースへのアクセス、Webサービスの呼び出し、データベースからのデータの取得など、複数の種類の操作が含まれます。プラットフォームと操作の種類に応じて、非同期I / Oは通常、ハードウェアまたは低レベルのシステムサポートを利用して操作を実行します。これは、CPUにできるだけ影響を与えずに実行されることを意味します。

アプリケーションレベルでは、非同期I / Oにより、I / O操作の完了をスレッドが待機する必要がなくなります。非同期I / O操作が開始されるとすぐに、それが起動されたスレッドが解放され、コールバックが登録されます。操作が完了すると、コールバックはキューに入れられ、最初に使用可能なスレッドで実行されます。

I / O操作が同期的に実行される場合、操作が完了するまで、実行中のスレッドは何も実行しません。ランタイムは、I / O操作がいつ完了したかを認識しないため、待機中のスレッドに定期的にCPU時間を提供します。CPU時間は、実際にCPUにバインドされた操作を実行する他のスレッドによって使用される可能性があります。

したがって、@ user1629468で述べたように、非同期I / Oの方がパフォーマンスは向上しませんが、スケーラビリティは向上します。これは、Webアプリケーションの場合のように、使用可能なスレッドの数が限られているコンテキストで実行する場合に明らかです。Webアプリケーションは通常、各リクエストにスレッドを割り当てるスレッドプールを使用します。長時間実行されるI / O操作で要求がブロックされると、Webプールが枯渇し、Webアプリケーションがフリーズするか、応答が遅くなるリスクがあります。

私が気づいたことの1つは、非同期I / Oが非常に高速なI / O操作を処理する場合の最良のオプションではないということです。その場合、I / O操作の完了を待つ間、スレッドをビジー状態にしないことの利点はそれほど重要ではなく、あるスレッドで操作が開始され、別のスレッドで操作が完了するという事実は、全体的な実行にオーバーヘッドを追加します。

ここで、非同期I / Oとマルチスレッドのトピックについて最近行った詳細な調査を読むことができます


完了すると予想されるI / O操作と、そうでない可能性があるもの(たとえば、リモートデバイスが到着するかどうかに関係なく、「シリアルポートに到着する次の文字を取得する」)を区別する価値はあるのでしょうか。何でも送ってください]。I / O操作が妥当な時間内に完了することが予想される場合は、操作が完了するまで関連リソースのクリーンアップを遅らせる可能性があります。操作は可能性がある場合は決して完成しない、しかし、このような遅延は無理だろう。
スーパーキャット2013年

@supercatあなたが説明しているシナリオは、低レベルのアプリケーションとライブラリで使用されています。サーバーは着信接続を継続的に待機するため、サーバーはこれに依存しています。上記の非同期I / Oは、特定の操作の開始とその完了のためのコールバックの登録に基づいているため、このシナリオに適合できません。説明している場合は、システムイベントにコールバックを登録し、すべての通知を処理する必要があります。操作を実行するのではなく、継続的に入力を処理しています。前述のように、これは通常低レベルで行われ、アプリではほとんど行われません。
Florin Dumitrescu 2013年

このパターンは、さまざまなタイプのハードウェアが付属しているアプリケーションではかなり一般的です。シリアルポートは以前ほど一般的ではありませんが、シリアルポートをエミュレートするUSB​​チップは、専用ハードウェアの設計でかなり人気があります。そのようなものからの文字はアプリケーションレベルで処理されます。これは、OSが入力文字のシーケンスが、たとえばキャッシュドロワーが開かれ、どこかに通知が送信されることを意味することを知る方法がないためです。
スーパーキャット2013年

ブロッキングIOのCPUコストに関する部分は正確ではないと思います。ブロッキング状態では、ブロッキングIOをトリガーしたスレッドはOSによって待機状態になり、IOが完全に完了するまでCPU期間を費やしません。 OS(割り込みによって通知)はブロックされたスレッドを再開しますか?あなたが説明したこと(ロングポーリングによるビジー待機)は、ほとんどのランタイム/コンパイラでブロッキングIOがどのように実装されているかではありません。
Lifu Huang

4

AIOを使用する主な理由は、スケーラビリティのためです。いくつかのスレッドのコンテキストで見ると、利点は明らかではありません。しかし、システムが数千のスレッドに拡張すると、AIOははるかに優れたパフォーマンスを提供します。警告は、AIOライブラリがそれ以上のボトルネックを導入するべきではないということです。


4

何らかの形のマルチコンピューティングによる速度の向上を推定するには、複数のCPUベースのタスクが複数のコンピューティングリソース(通常はプロセッサコア)で同時に実行されているか、またはすべてのタスクが同じリソース-つまり、一部のタスクは1つのシステムサブコンポーネント(ディスクストレージなど)に依存している場合もあれば、別のタスク(周辺デバイスからの通信の受信)に依存している場合もあれば、プロセッサコアの使用が必要な場合もあります。

最初のシナリオは、しばしば「並列」プログラミングと呼ばれます。2番目のシナリオは、「並行」または「非同期」プログラミングと呼ばれることが多いですが、「並行」は、オペレーティングシステムが複数のタスクの実行をインターリーブできるようにする必要があるかどうかに関係なく、そのような実行が必要かどうかに関係なく使用されることもあります直列に配置するか、複数のリソースを使用して並列実行を実現できる場合。後者の場合、「並行」とは、一般に、タスク実行の実際の同時性の観点からではなく、プログラムで実行が書き込まれる方法を指します。

これらすべてについて暗黙の仮定で話すのは非常に簡単です。たとえば、「非同期I / OはマルチスレッドI / Oよりも高速になる」などの主張をすぐに行う人もいます。この主張はいくつかの理由で疑わしい。まず、特定の非同期I / Oフレームワークがマルチスレッドで正確に実装されている場合が考えられます。その場合、それらは同じものであり、1つの概念が他の概念より「速い」と言っても意味がありません。 。

第2に、非同期フレームワークのシングルスレッド実装(シングルスレッドイベントループなど)がある場合でも、そのループが何を行っているかについて想定する必要があります。たとえば、シングルスレッドのイベントループでできるばかげたことの1つは、2つの異なる純粋にCPUにバインドされたタスクを非同期で完了するように要求することです。(最新のハードウェア最適化を無視して)理想的なシングルプロセッサコアのみを備えたマシンでこれを実行した場合、このタスクを「非同期に」実行しても、2つの独立して管理されたスレッドまたは1つの孤立したプロセスで実行する場合と実際に異なることはありません。 -違いは、スレッドコンテキストの切り替えまたはオペレーティングシステムのスケジュールの最適化にある可能性がありますが、両方のタスクがCPUに送られる場合、どちらの場合も同様です。

あなたが遭遇するかもしれない珍しいか愚かなコーナーケースの多くを想像することは役に立ちます。

「非同期」は、たとえば上記のように同時である必要はありません。ちょうど1つのプロセッサコアを備えたマシン上で、2つのCPUバウンドタスクを「非同期」に実行します。

マルチスレッド実行は同時である必要はありません。単一のプロセッサコアを備えたマシンで2つのスレッドを生成するか、2つのスレッドに他の種類の希少なリソース(たとえば、1つしか確立できないネットワークデータベース)を取得するように要求します。一度に接続)。スレッドの実行はインターリーブされる可能性がありますが、オペレーティングシステムスケジューラーは適切であるように見えますが、単一のコア(またはより一般的には、存在するスレッドよりも多くのスレッドを生成する場合)の合計ランタイムは削減できません(スレッドコンテキストの切り替えにより増加します)それらを実行するコア、またはリソースが維持できるものよりも多くのスレッドがリソースを要求する場合)。これと同じことがマルチプロセッシングにも当てはまります。

したがって、非同期I / Oもマルチスレッドも、実行時間に関してパフォーマンスを向上させる必要はありません。彼らは物事を遅くすることさえできます。

ただし、リモートデータベースなどのネットワーク接続されたリソースからデータを取得するためにネットワーク呼び出しを行い、ローカルCPUにバインドされた計算も行う特定のプログラムのように、特定のユースケースを定義すると、次のように推論できます。ハードウェアに関する特定の仮定を与えられた2つの方法のパフォーマンスの違い。

質問:実行する必要のある計算ステップの数と、それらを実行するためにリソースの独立したシステムの数はありますか?独立したシステムサブコンポーネントの使用を必要とし、同時に実行することでメリットを得られる計算ステップのサブセットはありますか?プロセッサコアの数と、複数のプロセッサまたはスレッドを使用して別々のコアでタスクを完了するためのオーバーヘッドはどのくらいですか?

タスクが主に独立したサブシステムに依存している場合は、非同期ソリューションが適している可能性があります。それを処理するために必要なスレッドの数が多く、コンテキストの切り替えがオペレーティングシステムにとって重要なものになる場合は、シングルスレッドの非同期ソリューションの方が適している可能性があります。

タスクが同じリソースにバインドされているときはいつでも(たとえば、同じネットワークまたはローカルリソースに同時にアクセスするための複数のニーズ)、マルチスレッドはおそらく不十分なオーバーヘッドを招き、シングルスレッドの非同期そのようなリソースでのオーバーヘッド少なくなる可能性があります。限られた状況でもそれはスピードアップを生み出すことができません。このような場合、唯一のオプション(高速化が必要な場合)は、そのリソースの複数のコピーを利用可能にすることです(たとえば、不足しているリソースがCPUの場合は複数のプロセッサコア)。不足しているリソースの場合、より多くの同時接続をサポートする優れたデータベース接続制限のあるデータベースなどです)。

別の言い方をすると、オペレーティングシステムが2つのタスクの単一リソースの使用をインターリーブできるようにすることは、1つのタスクにリソースを使用させ、他のタスクが待機し、2番目のタスクを順番に終了させるよりも速くはありません。さらに、インターリーブのスケジューラコストは、実際の状況では実際にスローダウンを引き起こすことを意味します。CPU、ネットワークリソース、メモリリソース、周辺機器、またはその他のシステムリソースが交互に使用されるかどうかは関係ありません。


2

非ブロッキングI / Oの1つの可能な実装は、ブロックI / Oを実行し、いくつかのコールバックメカニズムを介してI / Oの発信元のスレッドに通知するバックグラウンドスレッドのプールを使用して、まさにあなたが言ったことです。実際、これがglibc のAIOモジュールの動作方法です。ここでは、実装に関するいくつかの漠然とした詳細があります。

これは移植性の高い優れたソリューションですが(スレッドがある限り)、通常、OSは非ブロッキングI / Oをより効率的に処理できます。このWikipediaの記事では、スレッドプール以外の可能な実装をリストしています。


2

私は現在、プロトスレッドを使用して組み込みプラットフォームに非同期ioを実装しているところです。ノンブロッキングIOは、16000fpsと160fpsでの実行に違いをもたらします。ノンブロッキングIOの最大の利点は、ハードウェアが機能している間にコードを構造化して他のことを実行できることです。デバイスの初期化も並行して行うことができます。

マーティン


1

Nodeでは、複数のスレッドが起動されていますが、C ++ランタイムではレイヤーダウンです。

「つまり、NodeJSはシングルスレッドですが、これは真実の半分です。実際には、イベント駆動型であり、バックグラウンドワーカーでシングルスレッド化されています。メインイベントループはシングルスレッドですが、ほとんどのI / Oは別々のスレッドで実行されます。イベントループに対応するために、Node.jsのI / O APIは設計上非同期/非ブロッキングであるためです。」

https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea

「Node.jsは非ブロッキングです。つまり、すべての関数(コールバック)がイベントループに委任され、それらは異なるスレッドによって実行されます(または実行される可能性があります)。これはNode.jsランタイムによって処理されます。」

https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98 

「ノードは非ブロッキングなので高速です」という説明は少しマーケティングであり、これは素晴らしい質問です。効率的でスケーラブルですが、シングルスレッドではありません。


0

私の知る限りでは、非同期I / OはいわゆるI / O完了ポートを使用します(私はMSシステムについて話していますが、明確にするためです)。非同期呼び出しを使用することにより、フレームワークはそのようなアーキテクチャを自動的に活用し、これは標準のスレッド化メカニズムよりもはるかに効率的であると考えられます。個人的な経験として、スレッドをブロックするのではなくAsyncCallsを使用したい場合は、アプリケーションの反応を敏感に感じると言えるでしょう。


0

非同期I / Oが機能しないという反例を挙げましょう。以下で使用するboost :: asioに似たプロキシを作成しています。 https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp

ただし、私の場合のシナリオは、着信(クライアント側からの)メッセージが高速であるのに、(サーバー側への)発信が1つのセッションで遅いため、着信速度に対応するか、プロキシの合計スループットを最大化するために、 1つの接続での複数のセッション。

したがって、この非同期I / Oフレームワークは機能しなくなります。各スレッドにセッションを割り当てることにより、サーバーに送信するスレッドプールが必要です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.