バッファリング

前へ << 連続アクセス防止 文字コードとエンコーディング (1) >> 次へ

バッファリングとは

バッファとは、データの一時的な保存場所のことです。 バッファリングとは、バッファにデータを一時的に保存しておくことです。 例えば perl で print 文を使うと、影でバッファリングが 行われています。

例えば、画面に「abc」という文字を表示することを考えましょう。 プログラムが a を出力、b を出力、c を出力、と1文字ずつ 出力してもいいのですが、一度に「abcd」という文字列を 表示した方が速度があがります。「画面に文字を表示しろ」という 機能は OS が持っているのですが、その機能を 呼び出すこと自体 それなりの時間がかかるからです。

具体的に言うと、システムコールの呼び出しには それなりのオーバーヘッドがかかりますし、 システムコール呼び出しをきっかけとして プロセスの切替えが起こりますので、 極力システムコールは使わないようにすべきです。
また、www というのはネットワーク経由でデータを送受信していますが、 具体的には IP パケットという小さな単位に分割されてやりとりされます。 大抵の環境では、IP パケットの大きさは 1500 バイトくらいです。 15KB のファイルをやりとりする際は、10 回くらい IP パケットが飛ぶわけです。 ところが、同じ 15KB でも 1バイトのパケットを 15000 回送ってもいいわけです。

ただし IP パケットというのは、宛先やチェックサムなどで 1パケットあたり 約 20 バイトのヘッダが付きます。結局、両者を比べると、

と、20倍のデータ量になってしまいます。ここでも一度に多くのデータを送信した方が 効率がよいことがわかるでしょうか。

なるべく一度に多くのデータを送るために、細切れに送られてきたデータを 一時的に保存しておくことを「バッファリング」というのです。

IP パケットの最大の大きさは MTU という値によって代わってきます (多分)。 MTU というのはインタフェースの種類ごとに決まっています。 ダイヤルアップや Ethernet で LAN を組んでいるなら、 約 1500 バイトと考えていいでしょう。 …というようなことは、覚えなくていいです。なお、正確には IP パケットでなく、IP データグラムと言います。

バッファリングを実感する(1)

実際にバッファリングがどのように機能しているのかを調べてみましょう。

buffering-1.cgi (実行結果)

    1: #!/usr/local/bin/perl
    2: 
    3: $filename = "/tmp/buffering.$$";
    4: 
    5: print "Content-type: text/plain\n\n";
    6: print "$filename に ABC を書き込みます。\n";
    7: 
    8: open(OUT,">/tmp/buffering.$$");
    9: print OUT "ABC\n";
   10: 
   11: print "$filename を表示します。\n";
   12: 
   13: open(IN,$filename);
   14: print <IN>;
   15: close(IN);
   16: 
   17: print "↑には何も出力されていないはずです。\n";
   18: 
   19: close(OUT);
   20: 
   21: print "close しました。\n";
   22: 
   23: print "再度 $filename を表示します。\n";
   24: 
   25: open(IN,$filename);
   26: print <IN>;
   27: close(IN);
   28: 
   29: print "今度は ABC と表示されたはずです。\n";
   30: 
   31: unlink($filename);
    8: open(OUT,">/tmp/buffering.$$");
    9: print OUT "ABC\n";
   13: open(IN,$filename);
   14: print <IN>;
   15: close(IN);

バッファリングを実感する(2)

buffering-2.cgi (実行結果)

    1: #!/usr/local/bin/perl
    2: 
    3: # $Id: buffering-2.cgi,v 1.2 2004/06/20 12:57:27 zxr400 Exp $
    4: 
    5: print "Content-type: text/plain\n\n";
    6: 
    7: print "hogehoge\n";
    8: system("/bin/echo fugafuga");
    9: 
   10: exit 0;
このスクリプトは一見問題ないように見えます。実際、 perl-5.6 以降では問題ありません (このサーバは perl-5.6 なので正常に表示されるはずです)。

しかし perl-5.6 より前の環境で CGI として動かすと Internal Server Error になってしまいます。 でもコマンドラインから実行すると、

% ./buffering-2.cgi
Content-type: text/plain

hogehoge
fugafuga
と、perl のエラーも起こりませんし、ヘッダも正しく出力されています。

何が起こっているかは、ファイルにリダイレクトしてみるとわかります。

% ./buffering-2.cgi > out
% cat out
fugafuga
Content-type: text/plain

hogehoge
このように、Content-type の前に fugafuga が出力されています (perl-5.6 以降では発生しません)。 つまり
    5: print "Content-type: text/plain\n\n";
    7: print "hogehoge\n";
より前に、
    8: system("/bin/echo fugafuga");
の結果が出力されてしまっています (perl-5.6 以降では発生しません)

文字列の順序が逆転する理由は、print 文の結果がバッファリングされていて、 実際には出力されていないからです。system は内部で fork を呼び、 子プロセス (この場合は /bin/echo) を実行しますので、その子プロセスが

ではなぜコマンドラインから実行したきは問題ないのか。 なぜファイルにリダイレクトすると順序が逆転するのか。

これを知るにはバッファリング

perl-5.6 で system や `` などの内部で fork を呼び出す関数は、 自動的にバッファ内のデータをフラッシュするように仕様が変更されました。 これはこれでひとつ便利になったのでよいのですが、 このスクリプトは短くて、しかも初心者に「なんで?」 と思わせる絶好のサンプルだったので、ちょっと残念です。

入出力ライブラリ

perl で print 文を使うときも、バッファリングが行われています。

まず、perl には文字列を出力するのに、print と syswrite という 2種類の方法があることを知っていますか? C 言語で言うと、 それぞれ printf、write に相当します。 大事なのは、

ということです。混乱してしまうので、 今後は C 言語の関数で説明します。先程も述べたように、システムコールは (必要がないなら) なるべく使わないようにするべきです。 と言っても、CGI プログラムで write を使っている人はほとんど いないでしょうけど。ライブラリ関数とシステムコールの重要な違いを もう一つあげると、 ということです。 つまり「C 言語の printf 関数を使っても、最終的には (C 言語の) write が呼ばれる」 ことになります。 同様に、printf,fprintf,fgets,fwrite,putc,puts なども最終的に 必ず write システムコールを呼ぶわけです。

すると「なんで文字列を出力するのに 2種類の命令があるの?」 という疑問がわくかもしれません。 実は printf の役割は、「バッファリングを行うこと」です。

と言い切ると語弊があるかなぁ。できる限り関数群をライブラリに追い出して、 システムコールの数を少なくし、カーネルを最小限の大きさにする、という 意図もあるでしょう。
これらの入出力ライブラリでは、8KB のバッファを持っています。 printf 関数が呼ばれると、まずバッファに貯めておきます。 ファイルや画面にデータを出力するには write システムコールを呼ぶしかありませんが、 8KB のバッファが一杯になるまで write を使いません。 8KB のバッファが一杯になると初めて write を使って出力します。

3種類のバッファリング

行単位

前へ << 連続アクセス防止 文字コードとエンコーディング (1) >> 次へ

$Id: buffering.html,v 1.6 2004/06/25 17:29:13 zxr400 Exp $