先週はNew I/Oを紹介しました。今週からは実際に使ってみましょう。
ここで使用するサンプルは「ファイルのコピー」を行います。ストリームを使用したものが1種類とNew I/Oを使用したものが3種類,合計4種類のサンプルになります。
- サンプルのダウンロードfilecopy.zip
filecopy.zipにはソースコードとJDK 5.0 update 6でコンパイルしたクラスファイルが含まれています。
使い方はすべて同一で,引数にコピー元のファイルとコピー先のファイルを指定します。例えば,ストリーム版サンプルでsource.txtをdestination.txtにコピーする場合は次のようになります
> java StreamFileCopier source.txt destination.txt
いろいろなサイズのファイルをコピーしてみると,コピー速度の違いを感覚的に理解できるはずです。
とはいうものの,どの程度違うか具体的な数字がないとわからないですね。そこで,テストを行うためのCopyTestクラスも一緒にfilecopy.zipに含めておきました。
CopyTestクラスは,1000バイトのファイルから1億バイトのファイルまで16個のファイルを作成し,ストリームとNew I/Oでコピーの時間を計測します。それぞれの手法でまず10回コピーを行い,さらにもう10回コピーを行います。時間を計測するのは後半の10回分の時間です。
マイクロベンチマークなので,これだけで結論付けることはできませんが,だいたいの傾向はつかめるはずです。
サンプルの実行には通常電圧版のPentium M 1.86GHz,メモリー1GBのパソコンを使いました。また,OSにはLinuxディストリビューションであるFedora Core 4(カーネルは2.6.15)を使用しました。
実行結果を表1と図1に示します。単位はすべてミリ秒です。
表1 ファイル・コピーの比較
ファイルサイズ | ストリーム | New I/O | ||
---|---|---|---|---|
Direct Buffer | Mapped File | Transfer | ||
1,000 | 2 | 1 | 2 | 2 |
2,000 | 1 | 2 | 7 | 7 |
5,000 | 2 | 2 | 2 | 3 |
10,000 | 2 | 2 | 2 | 3 |
20,000 | 3 | 3 | 2 | 4 |
50,000 | 6 | 6 | 2 | 4 |
100,000 | 12 | 14 | 6 | 7 |
200,000 | 17 | 23 | 5 | 11 |
500,000 | 42 | 43 | 9 | 22 |
1,000,000 | 78 | 62 | 22 | 46 |
2,000,000 | 158 | 128 | 48 | 93 |
5,000,000 | 399 | 317 | 120 | 230 |
10,000,000 | 808 | 635 | 242 | 470 |
20,000,000 | 1,627 | 1,327 | 478 | 1,125 |
50,000,000 | 4,980 | 4,136 | 1,154 | 2,403 |
100,000,000 | 14,183 | 4,341 | 4,213 | 5,389 |
New I/Oを使うと,ほとんどの場合,ストリームを使った場合に比べて早く終わっています。特にファイルのサイズが大きくなればなるほど,ストリームを使った手法との差は大きくなります。逆にいえば,ファイルサイズが小さい場合は,それほど差はありません。
図1を見ると,いずれの手法もそれぞれほぼ直線になっており,その傾きはほとんど変わりありません。ただし,これは対数で表したグラフであることにご注意ください。
このサンプルだけで早急に結論を出すことにはあまり意味がありません。ただ,大量の入出力を行う場合はNew I/Oが適していることが,何となくでもご理解いただけたと思います。
3つの手法
New I/Oのサンプルは3種類ありますが,それぞれ手法が異なることは前述したとおりです。
それぞれの手法について簡単に説明を加えていきましょう。具体的なコードの記述に関しては,来週以降に解説します。
ダイレクトバッファ
一番はじめの手法がダイレクトバッファです。
New I/Oではバッファを利用して入出力を行います。バッファには配列を用いて実装されたものと,ヒープ外のメモリーに直接アロケートされるものの2種類があります。後者のことをダイレクトバッファと呼びます(前者には特に名前はありません)。
ダイレクトバッファはヒープ外のメモリーに直接にアロケートされるため,高速なアクセスが可能です。しかし,通常のバッファや配列に比べて,アロケーションのコストが高くなります。このため「ある程度の容量を,長期にわたって使用すること」が使うときのポイントになります。
サンプルでは4096バイトのダイレクトバッファ使い回してファイルのコピーを行っています。
ファイル・マッピング
2番目の手法がファイル・マッピングです。
ファイル・マッピングは,ファイルの特定の領域を直接メモリーにマッピングする手法です。ファイルをマップすると,ファイルをあたかもバッファのように読み書きできます。
ファイル・マッピングには,前述したダイレクトバッファが使用されるので,高速なアクセスが可能です。ただし,ファイル・マッピングにもダイレクトバッファと同様にマッピングに伴うコストが発生します。このため,ダイレクトバッファと同様に,ある程度大きな領域をマッピングする方が効果的です。
サンプルでは50MBまでは,ファイルのすべての領域を1度でマッピングしていますが,100MBではマッピングを2回に分けています。このため,100MBのファイルの時にはマッピングに時間がかかり,処理速度が低下しています。
トランスファ
最後の手法がトランスファです。
これはチャネルの出力を他のチャネルの入力に転送する機能です。他のチャネルの出力を,チャネルの入力にすることもできます。
トランスファのコードを記述すれば,それだけでチャネル同士の入出力を行えます。このため,入出力処理を記述せずに済み,とても簡潔にコードを記述できます。
このように,New I/Oを使うことにより,大量のデータを効率よく処理することが可能になります。このサンプルの結果を見たら,使わずにはいられないですよね。
ということで,来週からはNew I/Oを使ったコードの書き方を紹介していきます。
著者紹介 櫻庭祐一 横河電機の研究部門に勤務。同氏のJavaプログラマ向け情報ページ「Java in the Box」はあまりに有名 |