Indy サーバーはクライアント接続の処理に スレッド を使用します。新しいクライアントがサーバーに接続するたびに新しいスレッドが作成され、このスレッドが接続を処理します。100 個の接続があれば 100 個のスレッドが存在することになります。さらに Indy は ブロッキングソケット を使用しており、つまり読み取りまたは書き込みを行うと、その関数は完了するまで戻りません。
このモデルには、コードが順次処理されるためコーディングが容易、という利点があります。しかし欠点として、接続数が増えると スレッドコンテキストスイッチ によりパフォーマンスがどんどん悪化します。コンテキストスイッチとは、スレッドの状態を保存して、後で実行を再開できるようにするプロセスです。スレッド間の高速なコンテキストスイッチは CPU 使用率の点でコストがかかります。サーバーで 1000 個の接続を作成すると、データ交換が行われていなくても CPU が動作していることが分かります。この CPU 使用はスレッドコンテキストスイッチによるものです。
Indy モデルの代替手段
接続ごとに 1 つのスレッドを使用する代わりに、IOCP (Windows 用)や EPOLL (Linux 用)といった代替手段があります。これらはスレッドプールを使って接続を処理し、ノンブロッキングソケットを使用します。このモデルは同時接続数が多い場合にはるかに効率的で、デフォルトの Indy スレッドモデルよりはるかにうまくスケールします。
IOCP(Windows)
I/O 完了ポート(I/O completion ports)は、マルチプロセッサーシステム上で複数の非同期 I/O リクエストを処理するための効率的なスレッドモデルを提供します。プロセスが I/O 完了ポートを作成すると、システムはこれらのリクエストの処理を唯一の目的とするスレッド用に、関連付けられたキューオブジェクトを作成します。多くの同時非同期 I/O リクエストを処理するプロセスは、I/O リクエストを受け取ったときにスレッドを作成するよりも、I/O 完了ポートと事前に割り当てられたスレッドプールを組み合わせて使用するほうが、より迅速かつ効率的に処理できます。
IOCP モデルはノンブロッキングソケットを使用します。Select の代わりに AcceptEx 関数とスレッドプールを使ってクライアント接続を処理します。
sgcWebSockets Indy サーバーで IOCP を有効にするには、以下のコードを確認してください。
oServer := TsgcWebSocketHTTPServer.Create(nil); oServer.NotifyEvents := neNOSync; oServer.IOHandlerOptions.IOHandlerType := iohIOCP; oServer.Active := True;
新しい WebSocket + HTTP サーバーが作成され、スレッドプールが接続を処理します。使用されるスレッド数は、サーバーが実行されている CPU の数に依存します。
EPOLL(Linux)
Epoll はスケーラブルな I/O イベント通知機構のための Linux カーネルシステムコールで、Linux カーネルのバージョン 2.5.44 で初めて導入されました。 その機能は、複数のファイルディスクリプタを監視し、いずれかで I/O が可能かどうかを確認することです。これは、監視するファイルディスクリプタの数が多い、より要求の厳しいアプリケーションでより良いパフォーマンスを実現するため、古い POSIX の select および poll システムコールを置き換えることを目的としています。
10 万件の監視操作におけるパフォーマンスを比較した以下の表を確認してください。
| 操作数 | poll | select | epoll |
| 10 | 0.61 | 0.73 | 0.41 |
| 100 | 2.9 | 3.0 | 0.42 |
| 1000 | 35 | 35 | 0.53 |
| 10000 | 990 | 930 | 0.66 |
したがって、監視するファイルディスクリプタが 10 個程度を超えると、epoll の方が大幅に高速になります。
EPOLL モデルはノンブロッキングソケットを使用します。Select の代わりに Accept 非同期関数とスレッドプールを使ってクライアント接続を処理します。
sgcWebSockets Indy サーバーで EPOLL を有効にするには、以下のコードを確認してください。
oServer := TsgcWebSocketHTTPServer.Create(nil); oServer.NotifyEvents := neNOSync; oServer.IOHandlerOptions.IOHandlerType := iohEPOLL; oServer.Active := True;
