moratorium このページをアンテナに追加 RSSフィード

2006-05-13 最速cp on UNIX Systems このエントリーを含むブックマーク

ふとしたきっかけで、UNIX上における「最速cp」をやってみようと思い、いくつかの方法を実装してみた。

  • read -> write
  • read -> write with posix_fadvice
  • mmap -> mmap -> memcpy -> fsync
  • mmap -> mmap -> memcpy -> fsync with madvise
  • mmap -> mmap -> memcpy -> munmap
  • mmap -> mmap -> memcpy -> munmap with madvise
  • mmap -> write
  • mmap -> write with madvise

ソース

ソース

環境

Linux ubuntu 2.6.12-10-686 #1 Sat Mar 11 16:22:51 UTC 2006 i686 GNU/Linux
glibc 2.3.5-1ubuntu12

ベンチマーク

1Gのファイルを3回コピーし、その中で最速のものを取った。

./rw_cp ${SRC} ${DST}  0.07s user 8.42s system 5% cpu 2:28.12 total
./rw_fadv_cp ${SRC} ${DST}  0.05s user 6.54s system 4% cpu 2:37.58 total
./mm_sync_cp ${SRC} ${DST}  2.19s user 3.61s system 1% cpu 7:43.62 total
./mm_sync_madv_cp ${SRC} ${DST}  2.13s user 3.35s system 1% cpu 5:53.85 total
./mm_mun_cp ${SRC} ${DST}  2.23s user 3.09s system 1% cpu 6:56.09 total
./mm_mun_madv_cp ${SRC} ${DST}  2.14s user 2.69s system 1% cpu 5:59.98 total
./mw_cp ${SRC} ${DST}  0.00s user 5.25s system 3% cpu 2:51.23 total
./mw_madv_cp ${SRC} ${DST}  0.00s user 5.14s system 3% cpu 2:45.08 total

結果

  • read -> writeが一番早い
  • posix_fadviceはほとんど意味が無い
  • mmap with MAP_SHAREDした際にはmsyncで書き込むより、munmapで書き込んだ方が早い
  • madviseは効果が有る

感想

意外にもread -> writeが一番性能が出ているという結果になった。しかし納得が行かない。read -> writeだとkernel bufferからuser bufferへのコピーが発生するはずで、その分オーバーヘッドが発生する。mmap -> writeの場合はそういう事が起こらないはずなのだが、read -> writeよりも性能が出ない 。

何が起こっているのかよく分からない。カーネル読んでみてもちとよぅ分からず。もし知っておられる方がいらっしゃれば御教授お願いします...m(_ _)m他の環境でどうなるかも興味が有るので、もしよろしければベンチマークを取ってみて下さい。

理由が解明出来たらhtmlにして公開する予定。

ukaiukai 2006/05/13 04:07 http://ukai.org/d/index.cgi?2005-02-01#H-1r9v9l3

mmap->writeがおそいのは1Gもあるとpagefaultとかpagetableかきかえとかがコストかかってるとか?

kzkkzk 2006/05/13 04:26 あ、確かにcopy_to_user < pagefaultコストになってしまってる事は十分考えられますね。次はファイルサイズを変えて色々試してみます。コメント有難う御座います。
# どうせならファイルサイズ変えてグラフ化してみます

田畑田畑 2006/05/13 11:58 sendfileも使ってやってくださいな

田畑田畑 2006/05/13 12:20 ukaiさんのリンク先のとおりLinuxのsendfileでは無理ですね、すみません。
他に使えそうなシステムコールreadaheadを使うとどうでしょうか?

kzkkzk 2006/05/13 13:42 readaheadってposix_fadvise with POSIX_FADV_SEQUENTIALの動きを明示的に呼び出しているだけなので、変わらないんじゃないですかね。いや、実際に試してみずに言ってるんですが。

kzkkzk 2006/05/13 13:43 つかreadaheadの効率的な使いかたがよくわからんす...。readahead(fd, 0, size)する訳にも行かないすよねぇ..。

sodasoda 2006/05/13 20:05 ちょっと長いコメントで申し訳ないですが…

こういうのは、OSの種類によっても、またバージョンによっても、かなり結果が違いますから、いくつかのOSやバージョンで比べてみた方が良いと思います。
なお、munmap() を行なっても、実際にディスクに書き込むという保証はありません。msync() よりも munmap() の方が速いというのは、単に、ディスクに書き込む前にプログラムが完了しているだけであるという可能性が高いと思います。

テスト方法についてですが、ファイルを消去せずに書き込みファイルを再オープンしているので、2回目以降は既に1GB割り当てられたファイルの書き換え処理を計測していることになります。ファイルが大きくなると、O_TRUNC 自体にコストがかかることもあるので、出力ファイルは毎回消去した方が良いでしょう。
あと、mmap_write.c の madvise() の assert() は真偽の条件が逆になっています。このアサートに引っかからなかったということは、システムコールでエラーが生じてることになります。NetBSD で試したところ、assert() で落ちたので気づきました。

以下は実メモリ2GBの NetBSD-3.0/i386 で試した結果です。
(ファイルサイズよりも、メモリサイズの方が大きいので、メモリキャッシュの効果が大きい筈です。)
fadvise() は、NetBSD-current なら実装されているのですが、ここでは計っていません。
rw_cp 24.12 real 0.02 user 4.63 sys
mm_sync_cp 35.72 real 0.79 user 2.70 sys
mm_sync_madv_cp (1回目) 30.03 real 0.91 user 2.54 sys
mm_sync_madv_cp (2回目以降) 35.40 real 0.83 user 2.96 sys
mm_mun_cp 18.53 real 0.81 user 2.51 sys
mm_mun_madv_cp 17.85 real 0.80 user 2.70 sys
mw_cp 24.28 real 0.00 user 3.39 sys
mw_madv_cp 24.07 real 0.00 user 3.48 sys
入力ファイルと出力ファイルを別ディスクにして、iostat で観察しながら計りました。
mmap -> mmap -> memcpy -> munmap with or without madvise のケースが一番速い結果になっていますが、iostat によると、これらは 280MB 程度しか書き込みを行なわずに終了していました。ファイルがすぐに消去されるので、実際にはディスクに書き出さずにに終ってしまったのではないかと思います。msync している方は、当然ですが 1GB 書き込みを行なっています。

従って、NetBSD の場合、一番速いのは mmap -> write with madvise という、まあ妥当な結果になっています。でも read -> write でもほとんど変わりないですね。いまどきのマシンだとメモリコピーはディスク入出力の100倍くらい速いので、コピーのコストが隠れてしまっているのだと思います。もちろんメモリコピーするとキャッシュが汚れるといった副作用があるわけですが…
Linux の場合の違いが、page fault および page のすげかえからきているとすると、1ページあたり (2:45.08-2:28.12)/(1GB/4kB) = 64.7ms かかっている計算になりますが、これは、ちょっと大き過ぎる値のように思います。なにか別にオーバーヘッドがあるんじゃないでしょうか。
なお、NetBSD や Solaris の場合、read() や write() も、カーネル内部では mmap() と同等なことを行なっているので、page fault のコストは変わりません。

mm_sync_madv_cp は、1回目だけ、必ず5秒程度速いという結果になっています。これは、その直前に mm_mun_cp しているか、それとも msync しているかが効いているんだと思います。

sodasoda 2006/05/13 20:18 書き忘れましたが、上記は、出力ファイルを毎回 rm し、また madvise() の asssert() 条件を逆にして計測した結果です。

sodasoda 2006/05/14 00:34 > 64.7ms かかっている計算

すいません。ここはもちろん「64.7μs」の書き間違いです。
主旨は変わりませんが…

kzkkzk 2006/05/15 12:04 sodaさん、素晴らしいコメント有難う御座います。

まずは、確かにmadviseの条件が逆になっていました。逆にしてmadviseが正常に動く所までコードを変更しました。御指摘有難う御座います。

また、munmapを行ってもファイルが最後まで書かれていない現象も確認しました。すいません...。

「NetBSD の場合、一番速いのは mmap -> write with madvise という、まあ妥当な結果になっています。でも read -> write でもほとんど変わりないですね。いまどきのマシンだとメモリコピーはディスク入出力の100倍くらい速いので、コピーのコストが隠れてしまっているのだと思います。」なるほど、確かにそうですね。今のcpの実装は read -> write なんですが昔はどうだったかというのにも興味が湧いてきました。

「Linux の場合の違いが、page fault および page のすげかえからきているとすると、1ページあたり (2:45.08-2:28.12)/(1GB/4kB) = 64.7μs かかっている計算になりますが、これは、ちょっと大き過ぎる値のように思います。なにか別にオーバーヘッドがあるんじゃないでしょうか。」なるほど、勉強になります。とはいえ僕の知識だと何がオーバーヘッドになっているのか検討が付かず。

とりあえず時間が出来たら以上の点を修正し、グラフをアウトプットするようなスクリプトを書いてみます。それで色々な人の環境で試して貰うと面白い事が分かるんじゃないかと。

hyoshiokhyoshiok 2006/06/20 06:31 通りすがりのものです。oprofileをとってみて、比較したらいかがでしょう。どこで時間がかかっているか一発でわかります。