Scala で 基本的IOのパフォーマンス比較
Scala で IOのやり方の違いによる ファイルコピー パフォーマンスの比較
「Javaプラットフォームパフォーマンス」の 第4章の「基本的なIO」 にあるプログラムをScalaつかって比較してみました。
IOが遅くなる原因と性能を稼ぐ方法についてを Scala のプログラムでのファイルのコピーを題材に比較を行ってみました。
試した方式は
- FileInputStream/FileOutputStream を使った方式
- BufferedInputStream/BufferedOutputStream を使った方式
- 上記 + カスタムバッファを使った方式
- Scala IO を使った方式
の4つの方式で比較してみました。4つ目はScala 用の有名どころのライブラリを使った場合 Javaとどれくらい差があるのか を比較したいのでおこなってみました。
プログラム自体は 本のサンプルを参考に Scala 用に変更してみました。
ファイルの使用にあたり scala-arm をつかったローンパターンを使っております。
ことの発端
GW中に近所のブックオフ探索中に 「Javaプラットフォームパフォーマンス」という本を500円で購入した。
かなり 古い本ですが、 パフォーマンスチューニングの考え方として 使える内容だと思います。
500円なので とりあえず買いました。
もちろん Sunの時代の本なので、サンプルコードも 当時のURLから辿れません。
Oracleのサイトでもどこにあるのか、、、 わかりませんでした。
本の中で IOのパフォーマンス改良の章を実際に今時のマシンで試して 差がどれくらいあるのか確認したかった。
あと Scala でやってみたかった。
結論
バッファ使うと早い。
これは 当時も今も変わらない ということがわかった。
カスタムバッファ > Scala IO > BufferedInputStream > > > FileInputStream
Scala IO も 早い!!
最速のものは 一番遅いものに比較して 300倍ぐらいの差があった。
benchmark の一回目は どれも時間がかかっているので 2回目以降で比較すると 4000倍以上の差が数字上あったりする。。。
ググって出てくる コードの多くが バッファ使っているのは
この差を見れば 納得のいく結果。
マシンが速くなって 本の頃よりも 差が開いているようです。
DISKのIOがメモリに比べて圧倒的に遅いってことが実感されます。
SSDのマシンだと 違った結果になるのかな 今度 macbook air で試して見たいと思います。
環境
- mac mini 2012 モデル CPU: Corei7 2.3Ghz Mem:8GB HDD:1TB
- OS: Mac OSX 10.8.3
- scala version: 2.10.1
- scala-arm: 1.3.0
- scala-io: 0.4.2
計測方法
約1.8MB の mobi ファイルのコピーをおこないます。 時間の計測には scala の benchmark を使って計測をおこないます。 run 10 と実行し 10回計測し その平均値を出します。 これを3回実施
基本IOでの計測
FileInputStream を使った基本パターン
import scala.testing.Benchmark object JPPFIS extends Benchmark{ def copy(from:String, to:String):Unit = { import resource._ for { in <- managed(new java.io.FileInputStream(from)) out <- managed(new java.io.FileOutputStream(to)) }{ def loop { val readBytes = in.read() if(readBytes != -1) { out.write(readBytes) loop } } loop } } def run() = { copy("Testing.mobi","t1.mobi") } }
実行結果
[info] Running io.github.takuya71.JPPFIS 10 io.github.takuya71.JPPFIS$ 5035 4695 4763 4890 4875 4760 4705 4759 4702 4736 [info] Running io.github.takuya71.JPPFIS 10 io.github.takuya71.JPPFIS$ 4737 4695 4788 4814 4785 4840 4966 4847 4794 4998 [info] Running io.github.takuya71.JPPFIS 10 io.github.takuya71.JPPFIS$ 5350 5002 4702 4737 4723 4774 4766 4795 4795 4729
- 1回目 平均 4792 ms
- 2回目 平均 4826 ms
- 3回目 平均 4837 ms
約5秒ってことで結構かかってます。
BufferedInputStream を使ったパターン
import scala.testing.Benchmark object JPPBuf extends Benchmark{ def copy(from:String, to:String):Unit = { import resource._ for { in <- managed(new java.io.BufferedInputStream(new java.io.FileInputStream(from))) out <- managed(new java.io.BufferedOutputStream(new java.io.FileOutputStream(to))) }{ def loop { val readBytes = in.read() if(readBytes != -1) { out.write(readBytes) loop } } loop } } def run() = { copy("Testing.mobi","t1.mobi") } }
実行結果
[info] Running io.github.takuya71.JPPBuf 10 io.github.takuya71.JPPBuf$ 63 19 19 19 19 19 19 19 19 20 [info] Running io.github.takuya71.JPPBuf 10 io.github.takuya71.JPPBuf$ 36 20 19 19 19 19 19 19 19 20 [info] Running io.github.takuya71.JPPBuf 10 io.github.takuya71.JPPBuf$ 36 20 20 19 20 22 19 24 19 20
- 1回目 平均 24 ms
- 2回目 平均 21 ms
- 3回目 平均 22 ms
前のより 約250倍はやい BufferedInputStreamの力は大きい!
BufferedInputStream + Custom Buffer を使ったパターン
import scala.testing.Benchmark object JPPCustBuf extends Benchmark{ def copy(from:String, to:String):Unit = { import resource._ for{ in <- managed(new java.io.BufferedInputStream(new java.io.FileInputStream(from))) out <- managed(new java.io.BufferedOutputStream(new java.io.FileOutputStream(to))) }{ val buf = new Array[Byte](8192) // 8192 byte のバッファ def loop { val readBytes = in.read(buf) if(readBytes != -1) { out.write(readBytes) loop } } loop } } def run() = { copy("Testing.mobi","t1.mobi") } }
実行結果
[info] Running io.github.takuya71.JPPCustBuf 10 io.github.takuya71.JPPCustBuf$ 41 1 1 2 1 2 1 1 1 1 [info] Running io.github.takuya71.JPPCustBuf 10 io.github.takuya71.JPPCustBuf$ 12 1 1 2 2 1 1 1 1 1 [info] Running io.github.takuya71.JPPCustBuf 10 io.github.takuya71.JPPCustBuf$ 12 1 1 1 1 2 1 1 2 1
やっぱ Buffer 使うと早いです。
- 1回目 平均 5 ms
- 2回目 平均 2 ms
- 3回目 平均 2 ms
シビアな処理の時には バッファ使うべきですね。
Scala IO の copyTo を使ったパターン
import scala.testing.Benchmark import scalax.file.Path object JPPScalax extends Benchmark{ def copy(from:String, to:String) = { val in = Path.fromString(from) val out = Path.fromString(to) in.copyTo(out,replaceExisting=true) } def run() = { copy("Testing.mobi","t1.mobi") } }
実行結果
[info] Running io.github.takuya71.JPPScalax 10 io.github.takuya71.JPPScalax$ 103 5 6 6 5 5 5 6 5 5 [info] Running io.github.takuya71.JPPScalax 10 io.github.takuya71.JPPScalax$ 79 4 4 4 5 7 5 14 5 5 [info] Running io.github.takuya71.JPPScalax 10 io.github.takuya71.JPPScalax$ 78 5 5 5 5 7 6 6 4 6
おもったよりも 早いので 性能にシビアな使い方じゃなければ、
使いやすいし 早し、いいと思います。
- 1回目 平均 15 ms
- 2回目 平均 13 ms
- 3回目 平均 13 ms
追加テスト
100MBぐらいのファイルでもおこなってみました。
Scala IO のcopyTo の場合
[info] Running io.github.takuya71.JPPScalax 10 io.github.takuya71.JPPScalax$ 2350 1295 1112 978 866 1106 937 996 1147 1080
custom buf の場合
[info] Running io.github.takuya71.JPPCustBuf 10 io.github.takuya71.JPPCustBuf$ 81 78 70 61 56 34 39 34 34 34
サイズが大きくなると、結構差がつきますね。
Scala IO でもきつい状況には Buffer 使いましょう。
参考
Javaプラットフォームパフォーマンス―コードレベルのチューニングと開発プロセスへの統合 (The Java Series)
- 作者: スティーブウィルソン,ジェフケセルマン,Steve Wilson,Jeff Kesselman,豊福剛
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2002/07
- メディア: 単行本
- クリック: 3回
- この商品を含むブログを見る