* splice システムコール [#o5c0f2aa]

linux 上の splice システムコールについては,前から気になっていた.
で,重い腰を上げて色々突ついてみた,というおはなし.

** splice システムコールとは [#e6855d88]

指定した 2 つのファイルディスクリプタを(カーネル側で)接続して,「xx バイト転送せよ」と指示を出すシステムコールである.

詳しくは [http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/splice.2.html マニュアル]を参照のこと.

** 何が嬉しいのか [#c53ab361]
** read/write の場合 [#hda7f9cb]

例えば,ファイルのコピーをする場合,普通に考えると

+ 読み出し側ファイルを open
+ 書き込み側ファイルを open
+ 以下繰り返し
++ 読み出し側ファイルからデータを読み出し
++ 書き込み側ファイルにデータを書き込む

のようなプログラムになる.

この場合,カーネル空間からユーザ空間のバッファにいったんデータを持ってきて,さらにカーネル空間にデータを持っていく,という処理をしていることになる.

                             ^
 カーネル空間        |       |
 --------------------fd1----fd2-------------------------
 ユーザ空間          |       |
                     V       |
                     バッファ

** splice を使うと [#r32bb88f]

splice を使った場合のファイルコピーは
+ 読み出し側ファイルを open
+ 書き込み側ファイルを open
+ パイプを用意する
+ 以下繰り返し
++ 読み出し側ファイルからパイプへのデータ転送を splice で指示
++ パイプから書き込み側ファイルへのデータ転送を splice で指示

となる.

読み出し側ファイルと書き込み側ファイル間のデータ転送を直接せずに間にパイプを挟んでいるのは splice の制限のため(転送するファイルディスクリプタの片側はパイプでないといけない)である.

概念どおりに考えれば,やっぱりデータコピーが発生することになるんだけれど,データ転送はカーネル空間内で閉じていることになりデータコピーが最適化されてコピー回数が減るんじゃないか,とも考えられる.

 カーネル空間     fd1 -> ==(パイプ)== -> fd2
 ---------------------^---------------^---------------
 ユーザ空間           |               |
                   splice!          splice!

** パイプ詰まり? [#ld4302d1]

パイプを使ったプログラムを書いた経験のある人ならわかると思うけれど,パイプは''詰まる''ことがある.

つまり,パイプの読み出し側でデータを読み出してない状態で書き込み側からデータを書き込み続けると,「これ以上書き込めない」と書き込み側がブロックされる.
水道管の出口がふさがったまま水を流しつづけると,入り口側まで水が溜まってそれ以上水を流し込めなくなるのに似ている.

というわけで,最初はスレッドを使ってパイプの入り口と出口で並行して splice を発行するようなプログラムを書いていた.

が,[http://d.hatena.ne.jp/viver/20071011/p1 ここ]を見ると,1つのコンテキストで splice を交互に発行するようなプログラムになっている.
これだと「パイプ詰まりと詰まりの解消が交互に起きることになって,データがうまく流れないんじゃないかなぁ」とも思った.
これだと「パイプ詰まりと詰まり解消が交互に起きることになって,データがうまく流れないんじゃないかなぁ」とも思った.
が,こういう実例もあるからには,splice を使ったときのパイプの挙動も見てみたほうがいいのかな,と思い,splice 1回ごとのデータ転送量も見てみることにした.

** ベンチマーク [#v3ef951a]
#ref(test_splice.c)

というわけで,適当にベンチマークプログラムを作成.

見ているのは
+ read,write を繰り替えした転送
+ splice を単一スレッドで交互に呼び出した場合
+ splice を複数スレッドで同時に呼び出した場合

で,
- /dev/zero から /dev/null への転送
- 転送量は 16 GiB
- read, write でのバッファサイズは 16 MiB

という条件で実行してみた.

 $ ./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

転送速度を計算すると
||転送速度|h
|read/write|2.0 GB/s|
|splice 単一スレッド|4.3 GB/s|
|splice 複数スレッド|4.4 GB/s|

という結果になった.

splice を使うと read, write に比べて 2 倍以上の転送速度が得られている.
で,交互に splice を呼び出す場合と複数スレッドで同時に呼び出している場合では,パフォーマンスにはほとんど差が見られない.
少しは速いんだけれど,スレッドプログラミングの手間に見合わない感じ.

ところで,現在の普通の HDD でのデータファイルの転送速度は 100 MB/s ぐらいのオーダである.
で,read / write での実装でもこれよりも 1 桁上の転送速度が得られている.
つまり,HDD への読み書きに関して言えば,read/write から splice に置き換えたとしても,転送速度はほとんど変わらないんじゃないかと思われる.

** splice ごとの転送バイト数 [#lc43bdd3]

で,今度は splice の呼び出しの後に転送バイト数を出力するようにしてみた.
パイプの挙動を見るためである.

その結果…
> 単一スレッドで交互に呼び出しても,複数スレッドで同時に呼び出しても splice での転送サイズは 64KiB と一定

だった.

> パイプと言っても中身は単なるバッファだし,「スムースに流れている」パイプでもシステムコール〜コンテキスト切り替えのような短いタイムスパンで見れば,実は詰まりとその解消を繰り返している

という,考えてみれば当たり前の結論に達する.

** まとめ [#fdc3581e]

- splice を使うと,ファイルディスクリプタ間のデータ転送速度は,最高で 2 倍程度にまで持っていける
- が,ファイルアクセス自体の速度が遅ければ,ほとんど効果は無いと予想される
- 単一スレッドでの交互呼び出しと複数スレッドでの同時呼び出しの 2 通りを試してみたが,ほとんど差がない.単一スレッドで充分だろう.

** 参考 [#ob518546]

:JM project の日本語訳 man ページ|
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/splice.2.html
:splice()とvmsplice()を試す|
http://d.hatena.ne.jp/viver/20071011/p1

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS