Path: sran265!katsu From: katsu@sran14.sra.co.jp (Katsuhiro Watanabe) Message-ID: Date: 17 Mar 92 10:42:40 Organization: Software Research Associates, Inc.,Japan In-reply-to: katsu@sran14.sra.co.jp's message of 17 Mar 92 01:32:29 GMT Newsgroups: sra.os.unix Followup-To: sra.os.unix Subject: Re: The small packet probrem and its solution Distribution: sra References:  結論は、TCP の実装には、小さいパケットの抑制の為に Nagle の アルゴリズム(参考文献 [1][2][3][4])というのが採用されているが、 このアルゴリズムのために、新田さんのプログラムのような場合には 転送が遅くなることがあるということです。  新田さんのプログラムでの観測結果は、「write する長さによって TCP の 能力が大きく変わる」ことは全く意味しないので、恐れることはないでしょう。  以下に、ちょっと詳しく問題の解析をしてみます。(こいつの書く記事は いつも冗長で困るという人は、ここから参考文献に飛んでください) [1]問題は TCP プロトコルにある  新田さんのプログラムの要約を再び出します。 int datasize; char s[3000]; /* 送受信データの置き場所 */ /* 実際の新田さんのプログラムでは、ちゃんと全部読みきる/書ききるまで read(2)/write(2) を繰り返してる */ if (fork()) { for (;;) { read(socket, &datasize, sizeof(datasize)); read(socket, s, datasize); write(socket, &datasize, sizeof(datasize)); wrire(socket, s, datasize); } } else { struct timeval 始, 終; for (datasize = 100; datasize < sizeof(s); datasize += 100) { gettimeofday(&始, 0); write(socket, &datasize, sizeof(datasize)); wrire(socket, s, datasize); read(socket, &datasize, sizeof(datasize)); read(socket, s, datasize); gettimeofday(&終, 0); datasize および、終と始の時間差を表示; } }  見てわかる通り、単純にきまった大きさを read/write するのではなく、 大きさを 100 から 2900 まで変化させて調べるために、まえもってデータの 大きさ自身(おそらく 4 バイト)を受け送りします。  例えばこのプログラムで 100 バイトのデータを送る実験をする場合、 ソケット層より上の層から見ると、 子供 親 -> 4 byte ----- (1) -> 100 byte ----- (2) <- 4 byte ----- (3) <- 100 byte ----- (4) のようにデータが流れる訳です。  実際これをやらせてみると「遅い」わけですが、TCP プロトコルの中で どこで時間がかかっているかを子供の trpt で見ると、(NEWS OS の trpt は 昔からフラグを出していません………一体いつになったら直るのだろう?) 352 ESTABLISHED:output [c592201..c592205)@c5a1c01(win=4000),f,f> -> ESTABLISHED 352 ESTABLISHED:user SEND -> ESTABLISHED 352 ESTABLISHED:user SEND -> ESTABLISHED 362 ESTABLISHED:input c5a1c01@c592205(win=4000),f> -> ESTABLISHED 362 ESTABLISHED:output [c592205..c592269)@c5a1c01(win=4000),f,f> -> ESTABLISHED : : : : : のように、(1)の後、(2)の分の write を受け付けつつ(時刻 352 で user SEND が2つ並んでいることでわかる)それの送出を時刻 362 での (1) の分の送達確認(ACK)の受信まで引き延ばしています。この時刻の数字は 10ms 単位なので、パケット2つだけのために 100ms もかかってしまっていた ことになります。(これによって、問題は TCP のプロトコルにあり、 例えば mbuf ページ割り当ての問題のようなカーネルの振舞いなどとは 無関係であることが確認できました。)  一方で、データの長さが 3000 バイトで「速い」場合には、 251 ESTABLISHED:output [11cbaa01..11cbaa05)@11cca401(win=4000),f,f> -> ESTABLISH ED 251 ESTABLISHED:user SEND -> ESTABLISHED 251 ESTABLISHED:output [11cbaa05..11cbafdd)@11cca401(win=4000),f> -> ESTABLISHED 251 ESTABLISHED:output [11cbafdd..11cbb5b5)@11cca401(win=4000),f> -> ESTABLISHED 251 ESTABLISHED:user SEND -> ESTABLISHED 251 ESTABLISHED:input 11cca401@11cbb5b5(win=4000),f> -> ESTABLISHED 251 ESTABLISHED:output [11cbb5b5..11cbb5bd)@11cca401(win=4000),f,f> -> ESTABLISHED : : : : : のようになっていて、データの write をプロトコルが受けた瞬間に送出を しています。各段階で全く時間を消費していないこともわかりますね。 [2]原因は Nagle の手法が TCP プロトコルに実装されたこと  では、何故 (2) の 100byte の送出は遅延させられるのでしょうか? という疑問に焦点をしぼって本などを調べたら、「小さいパケットの抑制」 というTCP の実装技術がすぐに見つかりました。  今、遠隔のホストに rlogin して作業をしているものと思って下さい。 何か一つキーを押したなら、そのデータはただちに相手のホストに届いて ほしいことでしょう。ところが、キーに対応する 1byte を送るのに TCP のパケットを一つ送るのは不経済です。  そこで、いくつかのキーをひとまとめにして送出したくなりますが、 送出の間隔をうまく決めないと、ユーザーが大きな遅延を感じてしまう ことになります。  というわけで考え出されたのが Nagle の手法で、かいつまんでいうと、 あるパケットを出したら、それが送達確認(ACK)されるまでの間は新規に 小さなパケットを送り出さないという方法です。これならば、小さな パケットが出過ぎることがなく、送出が遅らされ過ぎることもありません。 TCP の実装にあたっては Nagle の手法を含むことが推奨されているようです (参考文献[2][3])。  新田さんのプログラムに戻ると、(1) を出した後、(1) の分が送達確認 されるまでの間は (2) の送出はできなかったというわけです。  ちなみに、X window system のように高度に対話的なアプリケーションの ために、小さいパケットの抑制をやらないようにするオプションの TCP_NODELAY というのがあります。これは setsockopt(..., getprotobyname("tcp")->p_proto , TCP_NODELAY, ...) のようなことをして設定することができます。  なお、小さいパケットの抑制がこの問題の本質であることの裏付けとして (A) TCP_NODELAY option を設定すると速い。 (B) write 2回の代わりに writev を使うと速い (C) データの長さを write する代わりに、あらかじめ親子双方で同じ長さを 使うように決めておくと速い。 (D) 長さを write した後データを複数回 write すると、データの write の 回数の増加に応じて速くなってゆく。 等が観測されています。  最後ですが、記事 <307@sran84.sra.co.jp> で utashiro@sran84.sra.co.jp (Kazumasa Utashiro) さんいはく > あまり確かなことは言えませんが、mbuf の cluster を使うかどう > かというのが影響しているような気がします。  mbuf の cluster って、ページをまるごと抱えている mbuf の ことですか?  よく知らないのですが、TCP_NODELAY オプションをつけると、 小さいパケットでも大きいパケットでも速度が変わらず、どちらでも 速いですから、mbuf の実装はこの問題と関係ないと思います。 参考文献 [1]John Nagle, "Congestion Control in IP/TCP Internetworks", RFC896, January 1984. Nagle のアルゴリズムの詳しい記述。特に、The small-packet problem および、The solution to the small-packet probrem の項。 [2]R.Braden, "Requirements for Internet Hosts -- Communication Layers", RFC 1122, October 1989. 特に、4.2.3.4 When to Send Data や、4.2.2.14 Data Communication の項。 [3]Douglas Comer,"Internetworking With TCP/IP: Principles, Protocols, and Architecture", Prentice-Hall, 1988, ISBN 0-13-470188-7. (翻訳:村井純,楠本博之,「TCP/IPによるネットワーク構築:原理・プロトコル・ アーキテクチャ」bit 1990年7月号別冊,共立出版) 特に、Appendix 2 Hints And Suggestions For Implementors の TCP の項の Nagle heuristic についての記述および、Appendix 4 の Nagle's algorithm の項。 [4]Leffler S.J., M.K.McKusick, M.J.Karels, and J.S.Quarterman, "The Design and Implementation of the 4.3BSD UNIX Operating System", Addison-Wesley, Reading, MA, 1989, ISBN 0-201-06196-1. (翻訳:中村明,相田仁,計宇生,小池汎平,「UNIX 4.3BSDの設計と実装」,丸善, 1991年,ISBN 4-621-03607-6) 手っ取り早く概要を知りたい人や、日本語で読みたい人はこれ。 特に、12.7 TCP Output Processing の Avoidance of Small Packets の節。 -- ----____----____ 渡邊克宏@ソフトウェア工学研究所(四谷)