web-dev-qa-db-ja.com

Node.jsが内部的にスレッドに依存している場合、本質的にどのように高速になりますか?

私はちょうど次のビデオを見ました: Introduction to Node.js まだ速度の利点を得る方法を理解していません。

主に、ある時点で、Ryan Dahl(Node.jsの作成者)は、Node.jsはスレッドベースではなくイベントループベースであると言います。スレッドは高価であり、利用する並行プログラミングの専門家に任せてください。

後で、彼は、内部に独自のスレッドプールを持つ基本的なC実装を持つNode.jsのアーキテクチャスタックを示します。したがって、明らかにNode.js開発者は、独自のスレッドを開始したり、スレッドプールを直接使用したりすることはありません...非同期コールバックを使用します。それだけ理解しています。

私が理解していないのは、Node.jsがまだスレッドを使用しているという点です...実装を隠しているだけなので、50人が50個のファイル(現在メモリにない)を要求すると50個のスレッドが必要ない場合、どのように速くなりますか?

唯一の違いは、Node.js開発者が内部で管理しているため、スレッド化された詳細をコーディングする必要はありませんが、その下でスレッドを使用してIO(ブロッキング)ファイルリクエストを処理していることです。

それで、あなたは本当に1つの問題(スレッド化)を取り、その問題がまだ存在している間それを隠すのではありませんか:主に複数のスレッド、コンテキスト切り替え、デッドロック...など?

私はまだここで理解していない詳細があるはずです。

278
Ralph Caraveo

実際、ここではいくつかの異なることが混同されています。しかし、スレッドは本当に難しいというミームから始まります。そのため、それらが難しい場合は、1)バグのためにスレッドを使用し、2)可能な限り効率的に使用しないようにスレッドを使用する可能性が高くなります。 (2)はあなたが尋ねているものです。

彼が提供する例の1つを考えてみてください。リクエストが発生し、クエリを実行し、その結果を使用して何かを実行します。標準的な手順で記述した場合、コードは次のようになります。

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

リクエストによって、上記のコードを実行する新しいスレッドが作成された場合、query()の実行中は何もしないで、そこにスレッドが置かれます。 (Ryanによれば、Apacheは元の要求を満たすために単一のスレッドを使用しているのに対し、nginxはそうではないので、彼が話している場合にはそれよりも優れています。)

さて、あなたが本当に賢いのであれば、クエリを実行している間に環境がオフになり、他の何かをすることができる方法で上記のコードを表現するでしょう:

query( statement: "select smurfs from some_mushroom", callback: go_do_something_with_result() );

これは基本的にnode.jsが行っていることです。あなたは基本的に、言語と環境、したがってクロージャーに関するポイントのために便利な方法で、環境が何を実行するのか賢いようにコードを飾ります。そのように、node.jsはnewではなく、非同期I/Oを発明したという意味ではありません(誰もこのようなことを主張しているわけではありません)が、それが表現される方法は少し新しい違う。

注:実行する環境とタイミングについて環境が賢明であると言うとき、具体的には、I/Oを開始するために使用したスレッドを使用して、他の要求または実行可能な計算を処理できることを意味します並行して、または他のパラレルI/Oを開始します。 (特定のノードが同じリクエストに対してより多くの作業を開始できるほど洗練されているわけではありませんが、アイデアは得られます。)

137
jrtipton

注!これは古い答えです。おおまかな概要ではまだ当てはまりますが、ここ数年でNodeが急速に開発されたため、一部の詳細が変更された可能性があります。

スレッドを使用している理由は次のとおりです。

  1. open()のO_NONBLOCKオプションはファイルに対して機能しません
  2. ノンブロッキングIOを提供しないサードパーティライブラリがあります。

非ブロックIOを偽造するには、スレッドが必要です。別のスレッドでIOをブロックします。これはい解決策であり、多くのオーバーヘッドを引き起こします。

ハードウェアレベルではさらに悪化します。

  • DMA を使用すると、CPUは非同期的にIOをオフロードします。
  • データは、IOデバイスとメモリの間で直接転送されます。
  • カーネルはこれを同期的なブロックシステムコールでラップします。
  • Node.jsは、ブロッキングシステムコールをスレッドでラップします。

これは単なる愚かで非効率的です。しかし、少なくとも機能します! Node.jsを楽しむことができるのは、イベント駆動の非同期アーキテクチャの背後にある見苦しくて厄介な詳細を隠すためです。

将来誰かがファイルにO_NONBLOCKを実装するでしょうか?...

Edit:友人とこれについて話し合ったところ、スレッドの代わりに select :タイムアウトを指定することでポーリングしていると彼に言ったの0および返されたファイル記述子に対してIOを実行します(ブロックされないことが保証されたため)。

32
nalply

ここで「間違ったことをしている」のではないかと心配しています。特に、何人かの人々が作成したきちんとした小さな注釈をどのように作成するのかがわかりません。しかし、私はこのスレッドについて多くの懸念/観察をしなければなりません。

1)人気のある回答の1つにある擬似コードのコメント要素

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

本質的に偽物です。スレッドが計算している場合、親指をいじるのではなく、必要な作業を行っています。一方、IOの完了を単に待機している場合、CPU時間を使用してnotである場合、カーネルのスレッド制御インフラストラクチャのポイントは、CPUが何か有用なものを見つけることです行う。ここで提案されている「親指をいじる」唯一の方法は、ポーリングループを作成することです。実際のWebサーバーをコーディングした人は誰もそれを行うのにふさわしくありません。

2)「スレッドは難しい」は、データ共有のコンテキストでのみ意味があります。独立したWebリクエストを処理する場合のように、本質的に独立したスレッドがある場合、スレッド化は非常に簡単で、1つのジョブを処理する方法の線形フローをコーディングし、複数のリクエストを処理することを十分に理解して、効果的に独立します。個人的には、ほとんどのプログラマーにとって、クロージャ/コールバックメカニズムの学習は、単にトップツーボトムスレッドバージョンをコーディングするよりも複雑だと思います。 (ただし、スレッド間で通信する必要がある場合、人生は本当に速く非常に難しくなりますが、クロージャー/コールバックメカニズムが実際に変更することは納得できません。このアプローチはスレッドでまだ達成可能であるため、オプションを制限するだけですとにかく、それはここでは実際には関係ない、まったく別の議論です)。

3)これまでのところ、特定のタイプのコンテキスト切り替えが他のタイプよりも多かれ少なかれ時間がかかる理由について、誰も実際の証拠を提示していません。マルチタスクカーネルを作成した私の経験(組み込みコントローラーの小規模で、「本物の」OSほど空想的なものはありません)は、そうではないことを示唆しています。

4)これまでに見てきたNodeが他のWebサーバーよりもどれだけ速いかを示すと主張するすべてのイラストは恐ろしく欠陥がありますが、1つの利点を間接的に示す方法で欠陥がありますNodeを確実に受け入れます(決して重要ではありません)。 Nodeは、チューニングが必要なようには見えません(実際には許可さえしません)。スレッドモデルを使用している場合、予想される負荷を処理するのに十分なスレッドを作成する必要があります。これをひどく行うと、パフォーマンスが低下します。スレッドが少なすぎると、CPUはアイドル状態になりますが、より多くのリクエストを受け入れることができず、作成するスレッドが多すぎるため、カーネルメモリが無駄になり、Java環境の場合は、また、メインヒープメモリを浪費しています。 Javaにとって、効率的なガベージコレクション(現在、これはG1で変更される可能性がありますが、2013年初頭の時点でまだ審査員が出ているようです。少なくとも)予備のヒープがたくさんあることに依存しています。そのため、問題があります。スレッド数が少なすぎると、CPUがアイドル状態になり、スループットが低下し、多すぎるとチューニングされ、他の方法で動きが取れなくなります。

5)Nodeのアプローチは「設計により高速である」という主張のロジックを受け入れる別の方法があります。それがこれです。ほとんどのスレッドモデルは、タイムスライスコンテキストスイッチモデルを使用します。これは、より適切な(値判定アラート:)およびより効率的な(値判定ではなく)先制モデルの上に階層化されます。これは2つの理由で発生します。1つは、ほとんどのプログラマーが優先順位の横取りを理解していないように見えることです。 ;特に、Javaの最初のバージョンは、Solaris実装で優先順位の優先順位を使用し、Windowsでタイムスライスを使用しました。ほとんどのプログラマーは「Solarisでスレッドが機能しない」どこでもタイムスライス)。とにかく、一番下の行は、タイムスライスが追加の(そして潜在的に不必要な)コンテキストスイッチを作成するということです。コンテキストの切り替えはすべてCPU時間を必要とし、その時間は実際のジョブで実行可能な作業から事実上削除されます。ただし、タイムスライスのためにコンテキストの切り替えに費やされる時間は、かなり奇妙なことが起こっている場合を除き、全体の時間の非常に小さな割合を超えてはなりません。また、そうなると予想できる理由はありません。シンプルなウェブサーバー)。そのため、はい、タイムスライスに関係する過剰なコンテキストスイッチは非効率的です(そして、これらはkernelスレッドとしては原則として発生しません)、違いはスループットの数パーセントであり、種類ではありませんNodeでしばしば暗示されるパフォーマンスの主張で暗示される整数因子の。

とにかく、そのすべてが長くて荒々しいことをおologiesびしますが、私はこれまでのところ、議論は何も証明していないと本当に感じており、これらの状況の誰かから聞いてうれしいです:

a)Nodeが優れている理由の本当の説明(上記の2つのシナリオを超えて、最初の(貧弱なチューニング)は私が見たすべてのテストの本当の説明だと思います) ([編集]実際、考えれば考えるほど、ここで膨大な数のスタックで使用されるメモリがかなり大きいのではないかと思うようになります。最新のスレッドのデフォルトのスタックサイズはかなり大きくなる傾向があります。ただし、クロージャーベースのイベントシステムによって割り当てられるメモリは、必要なものだけになります)

b)選択したスレッドサーバーに実際に公平なチャンスを与える実際のベンチマーク。少なくともそのように、私は主張が本質的に間違っていると信じるのをやめる必要があります;>([編集]それはおそらく私が意図したよりもかなり強いでしょうが、パフォーマンスの利点のために与えられた説明はせいぜい不完全で、示されているベンチマークは不合理です)。

乾杯、トビー

28
Toby Eggitt

私が理解していないのは、Node.jsがまだスレッドを使用しているという点です。

ライアンはブロックしている部分にスレッドを使用します(node.jsのほとんどは非ブロックIOを使用しています)。しかし、ライアンの願いは、すべてをブロックしないことです。 スライド63(内部設計) ライアンは、ノンブロッキング eventlooplibev (非同期イベント通知を抽象化するライブラリ)を使用していることがわかります。 event-loopのため、node.jsはより少ないスレッドを必要とし、コンテキストの切り替え、メモリ消費などを削減します。

14
Alfred

スレッドは、stat()などの非同期機能を持たない関数を処理するためにのみ使用されます。

stat()関数は常にブロックしているため、node.jsはスレッドを使用して、メインスレッド(イベントループ)をブロックせずに実際の呼び出しを実行する必要があります。潜在的に、これらの種類の関数を呼び出す必要がない場合、スレッドプールのスレッドは使用されません。

11
gawi

Node.jsの内部動作については何も知りませんが、イベントループの使用がスレッド化されたI/O処理をどのように上回るかを確認できます。ディスクリクエストを想像して、staticFile.xを渡して、そのファイルに対して100リクエストを作成します。通常、各リクエストはそのファイルを取得するスレッド、つまり100スレッドを占有します。

パブリッシャーオブジェクトになる1つのスレッドを作成する最初の要求を想像してください。staticFile.xのパブリッシャーオブジェクトがある場合、他の99のすべての要求が最初に検索します。新しいパブリッシャーオブジェクト。

シングルスレッドが完了すると、100個のリスナーすべてにstaticFile.xを渡し、それ自体を破棄するため、次のリクエストは新しいスレッドとパブリッシャーオブジェクトを新たに作成します。

したがって、上記の例では100スレッド対1スレッドですが、100ディスクルックアップの代わりに1ディスクルックアップでも、ゲインは非常に驚異的です。ライアンは賢い男です!

別の見方は、映画の冒頭での彼の例の1つです。の代わりに:

pseudo code:
result = query('select * from ...');

繰り返しますが、データベースに対する100の個別のクエリとは...:

pseudo code:
query('select * from ...', function(result){
    // do stuff with result
});

クエリが既に実行されていた場合、他の同等のクエリは単に時流に乗ってジャンプするため、1回のデータベースラウンドトリップで100クエリを実行できます。

7
BGerrissen