splice システムコール †linux 上の splice システムコールについては,前から気になっていた. で,重い腰を上げて色々突ついてみた,というおはなし. splice システムコールとは †指定した 2 つのファイルディスクリプタを(カーネル側で)接続して,「xx バイト転送せよ」と指示を出すシステムコールである. 詳しくは マニュアルを参照のこと. read/write の場合 †例えば,ファイルのコピーをする場合,普通に考えると
のようなプログラムになる. この場合,カーネル空間からユーザ空間のバッファにいったんデータを持ってきて,さらにカーネル空間にデータを持っていく,という処理をしていることになる. ^ カーネル空間 | | --------------------fd1----fd2------------------------- ユーザ空間 | | V | バッファ splice を使うと †splice を使った場合のファイルコピーは
となる. 読み出し側ファイルと書き込み側ファイル間のデータ転送を直接せずに間にパイプを挟んでいるのは splice の制限のため(転送するファイルディスクリプタの片側はパイプでないといけない)である. 概念どおりに考えれば,やっぱりデータコピーが発生することになるんだけれど,データ転送はカーネル空間内で閉じていることになりデータコピーが最適化されてコピー回数が減るんじゃないか,とも考えられる. カーネル空間 fd1 -> ==(パイプ)== -> fd2 ---------------------^---------------^--------------- ユーザ空間 | | splice! splice! パイプ詰まり? †パイプを使ったプログラムを書いた経験のある人ならわかると思うけれど,パイプは詰まることがある. つまり,パイプの読み出し側でデータを読み出してない状態で書き込み側からデータを書き込み続けると,「これ以上書き込めない」と書き込み側がブロックされる. 水道管の出口がふさがったまま水を流しつづけると,入り口側まで水が溜まってそれ以上水を流し込めなくなるのに似ている. というわけで,最初はスレッドを使ってパイプの入り口と出口で並行して splice を発行するようなプログラムを書いていた. が,ここを見ると,1つのコンテキストで splice を交互に発行するようなプログラムになっている. これだと「パイプ詰まりと詰まり解消が交互に起きることになって,データがうまく流れないんじゃないかなぁ」とも思った. が,こういう実例もあるからには,splice を使ったときのパイプの挙動も見てみたほうがいいのかな,と思い,splice 1回ごとのデータ転送量も見てみることにした. ベンチマーク †というわけで,適当にベンチマークプログラムを作成. 見ているのは
で,
という条件で実行してみた. $ ./a.out /dev/zero /dev/null read_write 計測開始 user = 0.000000, system = 8.412525 splice nothread 版計測開始 user = 0.044002, system = 4.056254 splice thread 版計測開始 user = 0.052004, system = 3.912244 転送速度を計算すると
という結果になった. splice を使うと read, write に比べて 2 倍以上の転送速度が得られている. で,交互に splice を呼び出す場合と複数スレッドで同時に呼び出している場合では,パフォーマンスにはほとんど差が見られない. 少しは速いんだけれど,スレッドプログラミングの手間に見合わない感じ. ところで,現在の普通の HDD でのデータファイルの転送速度は 100 MB/s ぐらいのオーダである. で,read / write での実装でもこれよりも 1 桁上の転送速度が得られている. つまり,HDD への読み書きに関して言えば,read/write から splice に置き換えたとしても,転送速度はほとんど変わらないんじゃないかと思われる. splice ごとの転送バイト数 †で,今度は splice の呼び出しの後に転送バイト数を出力するようにしてみた. パイプの挙動を見るためである. その結果…
だった.
という,考えてみれば当たり前の結論に達する. まとめ †
参考 †
|