Linuxのパイプ経由でデータをやり取りする際のバッファサイズを確認してみた

Bash

はじめに

command1 | command2 のような処理があった場合、 command1command2 の両プロセスは同時に立ち上がり、 command1 の出力内容はメモリに蓄えられます。
ただし、カーネルで設定されているパイプのバッファサイズ分のメモリしか利用されません。

command2 がバッファからデータをリードし、バッファに空きができるまで commans1 は待機します。

という仕組みをつたえたのですが、自分で言っていて自信がなかったので調べてみました。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)

Linuxのパイプ処理についておさらい

command1 | command2 のような処理について。

  1. command1command2 の両プロセスは同時に立ち上がる。
  2. command1 の出力内容はメモリに蓄えられる。
  3. カーネルで設定されているパイプのバッファサイズ分のメモリしか利用されません。
    • パイプのバッファサイズはデフォルトで 2**16 = 65536 bytes 。
  4. command1 はパイプのバッファに書き込めなくなった時点で「待ち状態」に入る。
      * command2 がデータを読み込み、空き容量ができると改めて command1 は動き出す。
  5. command1 はすべての出力内容をパイプのバッファに書き込めたタイミングで終了する。

これらを挙動を確認してみます。

検証

seq コマンドで256バイト(255文字の数値+改行コード1文字)を、パイプ経由で連続的に送り出します。
パイプでのデータ受信先では、 while read 構文を使い1秒おきに1行(256 bytes)読み込んでいきます。

同時に別のターミナルで seq コマンドの状態を確認します。

# seqコマンドプロセスが生まれるとターミナルに表示されるようになる。
# はじめは何も表示されていないはず。
$ watch -n 1 "ps aux| grep [s]eq"

検証

パイプのデフォルトバッファサイズピッタリのデータサイズを次のコマンドに送り込む場合

256バイトのデータを256行送信した場合は、パイプのデフォルトバッファサイズ 65536 となります。

# 確認
$ seq -f '%0255g' 1 256 \
  | wc -c
65536

# 実行
$ seq -f '%0255g' 1 256 \
  | while read f; do
    echo $f
    sleep 1
  done

このタイミングで watch コマンドを使って seq プロセスを監視している別ターミナルを確認しても、プロセスは表示されません。

おそらくですが、 seq コマンドが送信したデータサイズは、パイプのバッファサイズ以内だったため直ちに終了したのではないかと思います。

パイプのデフォルトバッファサイズを超えたのデータサイズを次のコマンドに送り込む場合

256バイトのデータを257行送信した場合は、パイプのデフォルトバッファサイズ 65792 となります。

# 確認
$ seq -f '%0255g' 1 257 \
  | wc

# 実行
$ seq -f '%0255g' 1 257 \
  | while read f; do
    echo $f
    sleep 1
  done

予想通り、 seq プロセス監視中のターミナルに変化が生じます

Every 1.0s: ps aux| grep [s]eq
502      32747  0.0  0.0 100920   716 pts/17   S+   08:00   0:00 seq -f %0255g 1 257

僕の予想では、while read プロセスが1行読み込んだ後、つまりバッファサイズが 256 bytes分空いたタイミングで seq がデータを送り込むスペースが生まれ、 seq プロセス終了、となると思っていました。

実際に確認してみると、何度やっても while read プロセスが16行読み込んだ後、つまりバッファサイズが 256 * 16 = 4096 bytes分空いたタイミングになるまで seq は終了しません。

000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016
# ※このタイミングでようやく `seq` プロセスが死ぬ

このあたりの仕組はもう少し勉強する必要があるなと感じました。

ひとこと

パイプでつないだ場合、パイプの前、パイプの後ろのプログラムの作りにもよりますが大量データを処理させても利用するメモリサイズはバッファサイズとなります。
メモリに優しい仕組みです。

Bash