情報アイランド

「情報を制する者は世界を制す」をモットーに様々な情報を提供することを目指すブログです。現在はプログラミング関連情報が多めですが、投資関連情報も取り扱っていきたいです。

Node.jsのパフォーマンス

2016/06/19

Node.jsを使用するとパフォーマンスが高いウェブアプリケーションを作成することができます。

では、何故パフォーマンスが高いウェブアプリケーションを作成することができるのでしょうか。

それはディスクやネットワークからのデータの読み込みには非常に時間が掛かるということが関係しています。

データを読み込むのに掛かる時間

様々な場所からデータを読み込むのに何CPUサイクルが掛かるかを比較してみましょう。

  • レベル1(L1)キャッシュ・・・3サイクル
  • レベル2(L2)キャッシュ・・・14サイクル
  • メモリ・・・250サイクル
  • ディスク・・・4100万サイクル
  • ネットワーク・・・2億4000万サイクル

このように、ディスクやネットワークからデータを読み込むのに掛かる時間とCPUキャッシュやメモリからデータを読み込むのに掛かる時間の間には雲泥の差があります。

ウェブアプリケーションはクライアントからリクエストを受ける度にディスクに保存されているデータを読み込んだり、ネットワーク越しにあるデータベースからデータを読み込んだりすることが多いです。

そのため、ディスクやネットワークからのデータの読み込みには非常に時間が掛かるということがウェブアプリケーションのパフォーマンスに大きな影響を与える可能性があります。

従来のウェブサーバ

従来のウェブサーバはクライアントからの1つ1つのリクエストを処理するのに毎回新しいプロセスを作成して使用していました。

しかし、プロセスの作成や終了は非常にコストの高い処理であり、時間も掛かりますし、多くのメモリも消費します。

ウェブアプリケーションはディスクやネットワークからデータを読み込むことが多いため、クライアントからのリクエストを処理するのにはそれなりに長い時間が掛かります。

したがって、リクエストを処理するプロセスはその間ずっと動き続けるということになり、プロセスが消費するメモリもそのまま消費されたままになるということになります。

そのため、従来のウェブサーバは同時に処理できるリクエストの数が非常に限られていました。

最近のウェブサーバ

従来のウェブサーバはパフォーマンス的に大きな問題がありましたので、最近のウェブサーバはクライアントからの1つ1つのリクエストを処理するのに毎回新しいプロセスを作成するということはせず、スレッドプールを活用するようになっています。

つまり、クライアントからの1つ1つのリクエストを処理するのにはスレッドプールのアイドル状態のスレッドを使用します。

これにより、プロセスの作成や終了という非常にコストの高い処理がなくなります。

また、プロセスに比べるとスレッドの方が消費するメモリの量も少ないため、メモリの節約にもなります。

しかし、複数のスレッドを使用しているため、スレッド間のコンテキストスイッチにある程度の時間が掛かりますし、ある程度のメモリも消費します。

メモリの消費量は同時に処理しているリクエストの数に大まかには比例し、同時に処理できるリクエストの数にはやはり限界があります。

Node.js

Node.jsでは読み込まれたJavaScriptコードは単一のスレッドで実行されます。

そのため、Node.jsでウェブサーバを作成するとクライアントからの1つ1つのリクエストを処理するのには単一のスレッドを使用することになり、スレッド間のコンテキストスイッチや複数のスレッドが消費するメモリによる余分なコストが掛かりません。

しかし、単一のスレッドしか使わないのであれば1つのリクエストを処理している間は別のリクエストを処理することができないということになってしまいます。

このように、ある処理が実行中のため別の処理が実行されるべきタイミングで実行されず、待機状態になってしまうことをスターベーションと言います。

Node.jsではスターベーションの問題を解決するためにイベントループを導入しています。

Node.jsにおける全ての処理単位は1つのタスクとしてイベントループに登録され、イベントループが回る度にその時点で登録されているタスクが順番に実行されます。

そして、ファイルシステムに対する処理やネットワークに対する処理のような時間の掛かる処理はJavaScriptコードを実行するスレッドとは別のスレッドで行い、イベントループが回る度に別スレッドでのそれらの処理が完了したかがチェックされるようになっています。

処理が完了していた場合には処理が完了した後に実行すべき処理がタスクとしてイベントループに登録されている場合にはそのタスクが実行されます。

そのため、このような、ある条件が満たされるのを待機し、条件が満たされた後に実行される処理はその処理の登録を行った元々の処理に対して非同期的に実行される非同期的な処理となります。

このようにすることで、時間の掛かる処理が別スレッドで行われている間もJavaScriptコードを実行するスレッドでイベントループが回り続け、イベントループに登録されているタスクを実行することができます。

つまり、1つのリクエストを処理している間であっても、ディスクやネットワークからデータを読み込んでいる間などでは別のリクエストを処理することができます。

これがNode.jsを使用するとパフォーマンスが高いウェブアプリケーションを作成することができる理由です。

ただし、忘れてはならないのは、あくまでもJavaScriptコードを実行するスレッドは単一であるということです。

そのため、時間の掛かるJavaScriptコードを1つの処理単位として記述した場合にはその処理が1つのタスクとしてイベントループで実行され、実行が完了するまでの間はイベントループが回ることはありません。

つまり、スターベーションが発生するということです。

そのため、Node.jsでプログラムを作成する場合には時間の掛かるJavaScriptコードを1つの処理単位として記述しないということが肝要です。

つまり、時間の掛かるJavaScriptコードは適切な大きさの複数の処理に分割し、適切なタイミングでそれぞれの処理をタスクとしてイベントループに登録し、イベントループを回して他の処理も行いながら非同期的に実行されるようにしなければなりません。

関連

pizyumi
プログラミング歴19年のベテランプログラマー。業務システム全般何でも作れます。現在はWeb系の技術を勉強中。
スポンサーリンク

-Node.js