通常、ソケット・アプリケーションを開発する際にまず必要となる作業は、ネットワーク環境の信頼性を確立し、必要な条件を整えることです。この記事で紹介する 4つのヒントを実行すれば、最適なソケット・アプリケーションを最初から設計・開発することができます。この記事では、ソケット API の使用法、アプリケーションの機能を拡張するソケット・オプション、ならびに GNU/Linux の調整方法について紹介します。
効率的なアプリケーションを開発するには、以下の 4つのヒントを実行します。
- パケット送信の待ち時間を最小限にする
- システム・コールのオーバーヘッドを最小限にする
- 帯域幅遅延積に対して TCP ウィンドウを調整する
- GNU/Linux の TCP/IP スタックを動的に調整する
TCP ソケットを使用して通信する場合、やり取りされるデータはブロック単位に分割され、通信用の TCP ペイロードに格納されます。TCP ペイロードのサイズはいくつかの要因 (パスに応じた最大パケット・サイズなど) によって決定されますが、通信が開始されるまでこれらの要因を特定することはできません。最高の性能を実現するには、それぞれのパケットにできるだけ多くの使用可能データを格納することが必要です。ペイロード内に十分なデータがない場合 (ペイロードのサイズが最大セグメント・サイズ (MSS) になります)、TCP は Nagle アルゴリズムにより、複数の小さなバッファーを自動的に連結して 1つのセグメントを作成します。これにより、アプリケーションの処理効率を上げ、小さなパケットの送信数を最小限に抑えてネットワーク全体の混雑を緩和します。
John Nagle が開発したこのアルゴリズムは、小さなパケットを連結してパケット数を最小限に抑える場合には有効ですが、単純に小さなパケットをそのまま送信したい場合もあるでしょう。たとえば Telnet のアプリケーションは、シェルを介してリモート・システムにアクセスできます。これが、パケットを送信する前に文字データを入力してセグメントに格納しなければならないとしたら、使いやすいアプリケーションとはいえないでしょう。
HTTP プロトコルの場合も、クライアント・ブラウザから HTTP 要求メッセージなどの簡単な要求を入力すると、Web サーバーから Web ページなどの複雑な結果が返ってくるのが通常です。
まず覚えておかなければならないのは、Nagle アルゴリズムは有効であるということです。TCP パケット・セグメントの容量いっぱいにデータを格納しようとするため、パケットの送信までにはどうしても待ち時間が発生してしまいますが、ネットワーク上でのパケット送信数を最小限に抑えるという利点があり、結果的にネットワーク全体の混雑を緩和することができます。
しかし、この送信待ち時間を最小限にしたい場合には、ソケット API を使用するのが有効です。Nagle アルゴリズムを無効にするには、リスト 1 のようにソケット・オプションの TCP_NODELAY を設定します。
リスト 1. Nagle アルゴリズムを無効にして TCP ソケットを使用する
int sock, flag, ret;
/* Create new stream socket */
sock = socket( AF_INET, SOCK_STREAM, 0 );
/* Disable the Nagle (TCP No Delay) algorithm */
flag = 1;
ret = setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) );
if (ret == -1) {
printf("Couldn't setsockopt(TCP_NODELAY)\n");
exit( EXIT_FAILURE );
}
|
参考: Samba を使用してテストしたところ、Nagle アルゴリズムを無効にした場合、Microsoft Windows サーバー上の Samba ドライブからのデータ読み込み速度がほぼ 2倍に上がることがわかりました。
ヒント 2. システム・コールのオーバーヘッドを最小限にする
ソケットのデータを読み込んだり書き込んだりする際には常にシステム・コールが呼び出されています。読み込みや書き込みといったコールは、ユーザーが使用するアプリケーション領域を超えてカーネルにまで到達します。カーネルに到達する前に、コールは C ライブラリを通ってカーネルの共通機能 (system call()) に送られます。次に、コールは system call() からファイルシステム層に送られ、ユーザーが使用しているデバイスの種類がカーネルによって判断されます。最後に、コールはソケット層に送られ、ここでデータが読み込まれたり、送信用やデータのコピー用としてソケット上の待ち行列に入れられたりします。
このプロセスから、システム・コールはアプリケーション領域やカーネル領域だけでなく、各領域のさまざまな階層で機能していることがわかります。このプロセスは複雑であるため、システム・コールの数が増えるほどコールの処理にかかる時間も長くなり、その結果、アプリケーションの処理能力も下がってしまいます。
システム・コールをなくすことはできないため、残された選択肢はシステム・コールの回数を最小限に抑えることになります。これを制御する方法を以下に説明します。
ソケットにデータを書き込む際には、データの書き込みを複数回に分けるのではなく、書き込み可能なデータをすべて同じタイミングで書き込むようにします。データを読み込む際には、できるだけ大きなバッファーに読み込むようにします。これは、十分な読み込みデータがある場合、カーネルは TCP の告知ウィンドウを開いたまま、バッファーをデータでいっぱいにしようとするためです。こうすることにより、システム・コールの回数を最小限にし、アプリケーション全体の性能を高めることができます。
ヒント 3. 帯域幅遅延積に対して TCP ウィンドウを調整する
TCP の性能は、いくつかの要因によって変わってきます。もっとも重要な要因は、リンク帯域幅 (ネットワーク上でパケットが送信されるレート) と往復遅延時間 (セグメントが送信されてから、相手の確認応答が戻ってくるまでの遅延時間。RTT ともいう) の 2つです。この 2つの値によって、帯域幅遅延積 (BDP) と呼ばれる値が決定されます。
リンク帯域幅の割合と RTT がわかれば BDP を算出することができますが、実際には何に使用するのでしょうか。BDP を使用すると、TCP ソケット・バッファーの理論上の最適サイズを簡単に算出できることがわかっています (このバッファーには、送信待ちと複製待ちの両方のデータが入ります)。バッファーのサイズが小さすぎる場合、TCP ウィンドウが完全には開かず、アプリケーションの機能が制限されてしまいます。逆にサイズが大きすぎる場合は、貴重なメモリーのリソースが無駄に消費されてしまいます。バッファーを最適なサイズに設定することにより、使用可能な帯域幅を最大限に活用することができます。以下に例を示します。
BDP = リンク帯域幅 × RTT
50ミリ秒の RTT で 100MBps を超える速度の LAN 環境でアプリケーションが通信する場合、BDP は以下のようになります。
100MBps * 0.050 sec / 8 = 0.625MB = 625KB
100MBps × 0.050秒 ÷ 8 = 625KB
注: ビットをバイトに変換するため、8 で割っています。
ここで TCP ウィンドウのサイズをこの BDP 値 (625KB) に設定するわけですが、Linux 2.6 での TCP ウィンドウのデフォルト・サイズは 110KB であるため、接続に使用される帯域幅は以下に示すように 2.2MBps に制限されてしまいます。
スループット = ウィンドウのサイズ ÷ RTT
110KB / 0.050 = 2.2MBps
110KB ÷ 0.050 = 2.2MBps
この値の代わりに上で算出した 625KB というウィンドウ・サイズを使用した場合、以下のように 12.5MBps という非常に大きなスループットを得ることができます。
625KB / 0.050 = 31.25MBps
625KB ÷ 0.050 = 12.5MBps
これは非常に大きな違いで、ソケットのスループットが劇的に大きくなります。これでソケットの最適なバッファー・サイズの算出方法がわかりました。では、どうすればこのように変更できるでしょうか。
ソケット API にはいくつかのオプションがありますが、そのうちの 2つのオプションを使用して、ソケットが送受信に使用するバッファーのサイズを変更することができます。リスト 2 では、SO_SNDBUF オプションと SO_RCVBUF オプションを使用して、ソケットが送受信に使用するバッファーのサイズを変更する方法を示しています。
注: ソケットのバッファー・サイズによって TCP の告知ウィンドウのサイズが決まりますが、告知ウィンドウ内には輻輳 (ふくそう) ウィンドウも配置されるため、ネットワークの混み具合によっては、特定のソケットが最大サイズの告知ウィンドウを使用できない場合があります。
リスト 2. ソケットが送受信に使用するバッファー・サイズを手動で設定する
int ret, sock, sock_buf_size;
sock = socket( AF_INET, SOCK_STREAM, 0 );
sock_buf_size = BDP;
ret = setsockopt( sock, SOL_SOCKET, SO_SNDBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size) );
ret = setsockopt( sock, SOL_SOCKET, SO_RCVBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size) );
|
Linux 2.6 のカーネルにおいては、送信用バッファー用のウィンドウのサイズにはユーザーが setsockopt コールで定義したサイズが使用されますが、受信用バッファーのサイズには自動的にその倍の値が設定されます。getsockopt コールを使用して、それぞれのバッファー・サイズを確認することができます。
ウィンドウ・スケーリングについてですが、当初 TCP は最大 64KB のウィンドウをサポートしていました (16ビット値がウィンドウ・サイズの定義に使用されていました)。ウィンドウ・スケーリング・オプション (RFC 1323) を組み込むことにより、ウィンドウ・サイズの定義に 32ビット値を使用できるようになりました。このオプションは、GNU/Linux で提供されている TCP/IP スタックでサポートされています (この他にも多くのオプションがサポートされています)。
参考: Linux のカーネルにはこうしたソケットのバッファーを自動的に調整する機能も含まれていますが (下に示した表 1 の tcp_rmem および tcp_wmem を参照)、これらのオプションを使用すると、スタック全体に影響を与えることになります。1つの接続のみ、または 1種類の接続のみに対してウィンドウを調整する必要がある場合にこの機能を使用すると便利です。
ヒント 4. GNU/Linux の TCP/IP スタックを動的に調整する
標準的な GNU/Linux 配布パッケージは、さまざまなアプリケーションを最適化の対象としています。そのため、システムの環境によっては、標準配布パッケージがその環境では最適でない場合があります。
GNU/Linux にはユーザーが変更することができるさまざまなカーネル・パラメータがあります。このパラメータを使用し、特定の目的にあわせてオペレーティング・システムを動的に設定することができます。ソケットの性能に影響を与える重要なオプションをいくつか見てみましょう。
変更可能なカーネル・パラメータは、/proc 仮想ファイルシステム内に存在しています。このファイルシステム内の各ファイルが 1つまたは複数のカーネル・パラメータにあたり、cat ユーティリティを介して読み込んだり、echo コマンドを使用して変更したりすることができます。リスト 3 では、変更可能なパラメータをクエリーする方法と有効にする方法を示しています (この例の場合、TCP/IP スタック内で IP フォワーディングを有効にする方法を示しています)。
リスト 3. 調整: TCP/IP スタック内で IP フォワーディングを有効にする
[root@camus]# cat /proc/sys/net/ipv4/ip_forward 0 [root@camus]# echo "1" > /proc/sys/net/ipv4/ip_forward [root@camus]# cat /proc/sys/net/ipv4/ip_forward 1 [root@camus]# |
表 1 は、Linux TCP/IP スタックの性能を向上させる変更可能なパラメータ (一部) の一覧です。
表 1. TCP/IP スタックの性能を向上させる変更可能なカーネル・パラメータ 変更可能なパラメータ デフォルト値 オプションの説明
| Tunable parameter | Default value | Option description |
|---|---|---|
/proc/sys/net/core/rmem_default
| "110592" | 受信用ウィンドウ・サイズのデフォルト値を定義します。BDP 値が大きい場合、ウィンドウのサイズも大きくします。 |
/proc/sys/net/core/rmem_max
| "110592" | 受信用ウィンドウ・サイズの最大値を定義するオプションです。BDP 値が大きい場合、ウィンドウのサイズも大きくします。 |
/proc/sys/net/core/wmem_default
| "110592" | 送信用ウィンドウ・サイズのデフォルト値を定義するオプションです。BDP 値が大きい場合、ウィンドウのサイズも大きくします。 |
/proc/sys/net/core/wmem_max
| "110592" | 送信用ウィンドウ・サイズの最大値を定義するオプションです。BDP 値が大きい場合、ウィンドウのサイズも大きくします。 |
/proc/sys/net/ipv4/tcp_window_scaling
| "1" | RFC 1323 の定義に従ってウィンドウ・スケーリングを有効にするオプションです。64KB を超えるサイズのウィンドウをサポートするよう設定する必要があります。 |
/proc/sys/net/ipv4/tcp_sack
| "1" | SACK 機能を有効にするオプションです。順不同で受信するパケットを選択的に受信確認することにより、送信元は足りないセグメントのみを再送信するだけでよくなるため、結果としてシステムの性能が向上します。このオプションは (広範囲にわたるネットワーク通信に対して) 有効にする必要がありますが、CPU 使用率が高くなる場合があります。 |
/proc/sys/net/ipv4/tcp_fack
| "1" | FACK 機能を有効にするオプションです。FACK は SACK とともに機能してネットワークの混雑を緩和します。このオプションは有効にします。 |
/proc/sys/net/ipv4/tcp_timestamps
| "1" | 再転送のタイムアウトの算出方法よりも正確な方法で RTT を算出するオプションです (RFC 1323 を参照)。このオプションはシステムの性能向上のために有効にします。 |
/proc/sys/net/ipv4/tcp_mem
| "24576 32768 49152" | メモリー使用量に対する TCP スタックの動作を決定するオプションです。それぞれの値はメモリー・ページのサイズを表します (通常は 4KB)。最初の値は、メモリー使用量の最小しきい値を表します。次の値は、メモリー圧縮モードを開始してバッファー使用量を圧縮する際のしきい値を表します。最後の値は、メモリー使用量の最大しきい値を表します。最大しきい値に達すると、メモリー使用量を減らすためにパケットが破棄されます。BDP 値が大きい場合はこの値も大きくします (ただし、この値はメモリー・ページのサイズであって、メモリーの容量ではないことに注意してください)。 |
/proc/sys/net/ipv4/tcp_wmem
| "4096 16384 131072" | 自動調整に必要な 1 ソケットあたりのメモリー使用量を定義するオプションです。最初の値は、ソケットの送信用バッファーに割り当てられている最小バイト数を表します。次の値は、システムに過大な負荷がかかっていない状態で、バッファーのサイズをどこまで大きくできるかのデフォルト値を表します (wmem_default によって上書きされます)。最後の値は、送信用バッファー・スペースの最大値を表します (wmem_max によって上書きされます)。 |
/proc/sys/net/ipv4/tcp_rmem
| "4096 87380 174760" | tcp_wmem と同様のオプションです。ただし、tcp_wmem は自動調整の際に受信用バッファーを参照します。 |
/proc/sys/net/ipv4/tcp_low_latency
| "0" | TCP/IP スタックに対して短い待ち時間を許可し、スループットを向上させるオプションです。このオプションは無効にします。 |
/proc/sys/net/ipv4/tcp_westwood
| "0" | 送信側の輻輳制御アルゴリズムを有効にするオプションです。このアルゴリズムはスループットの概算値を算出し、帯域幅全体の稼働率を最適化します。このオプションは WAN の通信用に有効にします。 |
/proc/sys/net/ipv4/tcp_bic
| "1" | 高速の長距離ネットワーク用に BIC 機能を有効にするオプションです。ギガビットの速度で稼働しているリンクの稼働率を上げます。このオプションは WAN の通信用に有効にします。 |
他の設定を調整する場合にも同じことが言えますが、パラメータの調整で一番大事なことは、いろいろな設定で試してみるということです。アプリケーションの動作、プロセッサの速度、メモリーの使用率によって、これらのパラメータがシステムの性能をどう変えるかが決まります。システムの性能を上げるつもりが、逆に下げてしまう場合があるかもしれません (またはその逆の場合もあるかもしれません)。ですから、オプションを 1つずつ実行し、その結果を確認するのが一番確実な方法です。この表を基にして検証を重ねてください。
参考: パラメータの保存方法について説明します。設定を変更したカーネル・パラメータは、GNU/Linux システムを再起動すると元のデフォルト値に戻ってしまうことに注意してください。変更したパラメータをデフォルト値として設定する場合、/etc/sysctl.conf ファイルを使用してシステムの起動時にカーネル・パラメータを設定してください。
GNU/Linux には便利なツールが数多くあります。コマンドライン・ツールが圧倒的に多いのですが、これは非常に便利でわかりやすいツールです。ネットワーク・アプリケーションのデバッグ・ツール、帯域幅/スループットの測定ツール、リンク稼働率のチェック・ツールなどがあります。これらのツールは、ネイティブ環境またはオープン・ソース環境で実行します。
表 2 は、最も便利な GNU/Linux ツールとその使用目的についての一覧です。表 3 は、GNU/Linux 配布パッケージには通常含まれない便利なツールの一覧です。この表のツールの詳細については、「参考文献」のセクションを参照してください。
表 2. GNU/Linux 配布パッケージに通常含まれているネイティブ・ツール GNU/Linux ユーティリティの使用目的
| GNU/Linux utility | Purpose |
|---|---|
ping
| ホストへのアクセスを確認するための最も一般的なツールです。BDP の計算に必要な RTT を算出する際にも使用できます。 |
traceroute
| 一連のルーターやゲートウェイを通ってネットワーク・ホストへ到達するまでの接続のパス (ルート) を画面に表示するツールです。各ホップ間の待ち時間も特定します。 |
netstat
| ネットワークのサブシステム、プロトコル、および接続についてのさまざまな統計を特定するツールです。 |
tcpdump
| 1つまたは複数の接続に対するプロトコル・レベルでのパケット追跡を表示するツールです。パケットのタイミング情報も表示されます。このタイミング情報を使用して、さまざまなプロトコル・サービスのパケットのタイミングを調査することができます。 |
表 3. GNU/Linux 配布パッケージには通常含まれない便利なパフォーマンス・ツール GNU/Linux ユーティリティの使用目的
| GNU/Linux utility | Purpose |
|---|---|
netlog
| ネットワーク性能に対するアプリケーション監視機能を提供するツールです。 |
nettimer
| ボトルネックとなっているリンク帯域幅に対するメトリックを生成するツールです。プロトコルの自動調整にも使用できます。 |
Ethereal
| 使いやすいグラフィック・インターフェースによる tcpump 機能 (パケット追跡) を提供するツールです。 |
iperf
| TCP および UDP のネットワーク性能を測定するツールです。最大帯域幅を測定し、遅延、ジッター、データグラムの損失もレポートします。 |
trafshow
| ネットワーク・トラフィックをフルスクリーンで視覚化します。 |
この記事では、ソケット・アプリケーションの性能を高めるヒントとテクニックについて見てきました。Nagle アルゴリズムを無効にして転送待ち時間を最小限にする方法、バッファー・サイズを変更してソケットの帯域幅の稼働率を上げる方法、システム・コールの数を少なくしてシステム・コールのオーバーヘッドを減らす方法、カーネル・パラメータを変更して Linux TCP/IP スタックを調整する方法などを実際に試してみてください。
アプリケーションの設定を調整する場合、常にそのアプリケーションの性質を考慮しなければなりません。たとえば、そのアプリケーションは LAN 環境で稼働するのか、あるいはインターネットを介して稼働するのか、といったことを考慮する必要があります。LAN 環境だけで稼働するアプリケーションの場合、ソケットのバッファー・サイズを大きくしてもあまり効果は期待できませんが、ジャンボ・フレームを有効にした場合は高い効果が得られるはずです。
最後になりましたが、設定を調整した場合は、tcpdump や Ethereal といったツールを使用して必ず結果を確認してください。調整の結果をパケットのレベルで確認することにより、ここで紹介したテクニックをどのように応用すればよいのかがわかってくるはずです。
学ぶために
- 2回シリーズのチュートリアル、「Programming Linux sockets」 (developerWorks, 2003年 10月と2004年 1月) は、ソケット・アプリケーションを書くために役立つでしょう。
- 他にも、Pittsburgh Supercomputing Center が、TCP に有利な輻輳制御アルゴリズムを紹介しています、
- MTU を増加することによって、パフォーマンスを大幅に改善することができます。ジャンボ・フレームと、その利点について学んでください。
- ICSI Center for Internet Research による、肯定応答に関する資料を読んでください。
- TCP Westwood home page を見て、TCP Westwoodアルゴリズムの詳細を学んでください。
- North Carolina State University による、Binary Increase Congestion TCP について調べてください。
- trafshow ユーティリティーは、IP 通信のトラフィック監視に便利です。
- developerWorks Linux ゾーンには、Linux 開発者のための資料が他にも豊富に用意されています。
- developerWorks technical events and Webcasts を利用して、最新技術を学んでください。
製品や技術を入手するために
- Ethereal は、プロトコル構文解析用のプラグイン・アーキテクチャーを持った、グラフィカルなネットワーク・プロトコル・アナライザーです。
- National Laboratory for Applied Network Research で、Iperf tool について学んでください。
- developerWorks から直接ダウンロードできる IBM ソフトウェア試用版を使って、皆さんの次期 Linux 開発プロジェクトを革新してください。
議論するために
- developerWorks blogs を見てください。そして developerWorks のコミュニティーに加わってください。
