Linuxシェルのパイプはメモリに優しい仕組みだった(バッファリングを学ぶ)
はじめに
先日、メンバからLinuxシェルにおけるパイプ機能について質問がありました。
- パイプで繋いだ前後のプロセスは同時に起動する?
- パイプで繋いだ前後のプロセスで、データのやり取りのためにどのぐらいのメモリを消費するのか?( 大量のデータを流した場合を懸念 )
回答したあとに、自分で言っていたことに少し自信がなかったので調べてみました。
特にメモリがどの程度使われるのか?について触れることがなかったので、実際にコマンドを実行しながら学習しました。
検証環境
$ uname -smoi
Darwin x86_64 MacBookPro16,1 Darwin
$ bash -version | head -n 1
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
$ seq --version | head -n 2
seq (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
Linux のパイプ処理の特徴についておさらい
command1 | command2
のようなパイプでつないだ処理の特徴をあげてみます。
command1
とcommand2
の両プロセスは同時に立ち上がるcommand1
はメモリバッファに随時出力を書きだすcommand2
はメモリバッファから随時入力を読み取る- カーネルで設定されているパイプのバッファサイズ分しかメモリを消費しない
command2
がバッファからデータを読み取り、バッファに空きができるまでcommand1
は書き出しを待機する
パイプのバッファサイズは、Linux 2.6.11以降デフォルトで 65536 bytes ( 4096 bytes/page * 16 pages
) となっています。
パイプにおけるバッファリングの理解は正しいのでしょうか?
大量データを処理したとしても、たったこれだけしかメモリを使用しないのでしょうか?
実際に確認してみます。
検証してみる
seq
コマンドで 1 行 256 bytes のデータ ( 数値 255 bytes + 改行コード 1 bytes ) を、パイプ経由で随時送り出します。
パイプでのデータ受信先では、 while read
構文を使い 5 秒おきに 1 行 ( 256 bytes ) ずつ読み込んでいきます。
同時に別のターミナルで seq
コマンドの状態がどう変化するかを監視します。
その1: 「パイプのバッファサイズちょうど」のデータサイズを次のコマンドに送り込む場合
256 bytes のデータを 256 行 送信した場合のデータ量は、パイプのバッファサイズと同じサイズの 65536 bytes となります。
# こんなデータを送り込んでみます。
# 先頭3行を確認。
$ seq -f '%0255g' 1 256 | head -n 3
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
# 末尾3行を確認。
$ seq -f '%0255g' 1 256 | tail -n 3
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000254
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000255
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000256
# データサイズを確認
$ seq -f '%0255g' 1 256 \
| wc -c
65536
# 実行
$ seq -f '%0255g' 1 256 \
| while read f; do
echo $f
sleep 5
done
コマンド実行中に別ターミナルを起動しておき、 watch
コマンドを使って seq
プロセスを監視しておきましたが、何も表示されません。
# seqコマンドプロセスが生まれると表示されるようになるはずだが、何も表示されない。
$ watch -n 1 "ps aux| grep [s]eq"
seq
コマンドが送信したデータサイズは、パイプのバッファサイズに収まるサイズでした。
そのため、 seq
プロセスは実行と同時にすべての出力ができたため、直ちに終了したものと思われます。
その2: 「パイプのバッファサイズ+1 行」のデータサイズを次のコマンドに送り込む場合
今度は、先程より 1 行だけ多い 257 行 送信してみます。
送信されるデータ量は、パイプのバッファサイズ ( 65536 bytes ) を超えた 65792 bytes となります。
# データサイズを確認
$ seq -f '%0255g' 1 257 \
| wc -c
65792
# 実行
$ seq -f '%0255g' 1 257 \
| while read f; do
echo $f
sleep 5
done
先程同様、 watch
コマンドを使って seq
プロセスを監視しておきましたが、何も表示されません。
# seqコマンドプロセスが生まれると表示されるようになるはずだが、何も表示されない。
$ watch -n 1 "ps aux| grep [s]eq"
以下のような流れで処理が進んだと思われます。
seq
が 256 行 分のデータをバッファに書き込み- ほぼ同時に
while read
が 1 行 分のデータを読み込み seq
はバッファに空きができたため、更に 1 行分のデータをバッファに書き込みし、処理を終了
この場合でも、やはりすべての出力ができたため、 seq
プロセスは直ちに終了したものと思われます。
その3: 「パイプのバッファサイズ+2 行」のデータサイズを次のコマンドに送り込む場合
最後に 256 bytes のデータを、更に 1 行 増やし、 258 行 送信してみます。
# データサイズを確認
$ seq -f '%0255g' 1 258 \
| wc -c
66048
# 実行
$ seq -f '%0255g' 1 258 \
| while read f; do
echo $f
sleep 5
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 258
コマンド実行後、5秒立つと seq
プロセスが終了し、 watch
から見えなるなるはずです。
想定通りの挙動をしていて安心しました。
ひとこと
パイプでつないだ場合、大量データを処理させてもパイプのやり取りで消費するメモリはバッファサイズとなります。
メモリに優しい仕組みです。
ディスカッション
コメント一覧
まだ、コメントがありません