IO.copy_streamで出力先がsocketでない場合でもsendfile()を使う

※結論から言うと速くならなかった。

rubyのIO.copy_streamは可能であればsendfile()を用いるが、これは出力先がsocketである場合に限定されていた。
Linux 2.6.33以降ではsendfile()の出力先が通常のファイルでも許されるようになっていた( sendfile(2) - Linux man page )ので、試してみた。
(実行した環境は、Ubuntu 11.10 3.0.0-15-server)

diff --git a/io.c b/io.c
index f404b53..181897f 100644
--- a/io.c
+++ b/io.c
@@ -9424,8 +9424,6 @@ nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
         stp->error_no = errno;
         return -1;
     }
-    if ((dst_stat.st_mode & S_IFMT) != S_IFSOCK)
-        return 0;
 
     src_offset = stp->src_offset;
     use_pread = src_offset != (off_t)-1;

このpatchをtrunkに適用すると通常のファイル同士のコピーでもsendfile()を使うようになる。
(実際にsendfile()を使っているかどうか確認するにはstraceが便利)

dd if=/dev/zero of=dummy bs=1M count=100

として作成したファイルを使って

require 'benchmark'

Benchmark.bm do |x|
  x.report do
    IO.copy_stream("dummy", "dst")
  end
end

このようなベンチマークを実行した。
それぞれ実行前に

sudo sysctl -w vm.drop_caches=1

した上での結果である。

trunk(r34734) # read() and write()

       user     system      total        real
   0.000000   0.360000   0.360000 (  0.702267)

       user     system      total        real
   0.000000   0.370000   0.370000 (  0.755613)

       user     system      total        real
   0.000000   0.390000   0.390000 (  0.724757)

proposal # sendfile()

       user     system      total        real
   0.000000   0.330000   0.330000 (  0.713097)

       user     system      total        real
   0.000000   0.400000   0.400000 (  0.737590)

       user     system      total        real
   0.000000   0.350000   0.350000 (  0.717704)

速くならなかった。
man pageには

Because this copying is done within the kernel, sendfile() is more efficient than the combination of read(2) and write(2), which would require transferring data to and from user space.

とあるけれど、通常のファイル同士のコピーではそれほど恩恵が得られないようだ。