1. ホーム
  2. java

[解決済み] Java NIOのFileChannelとFileOutputstreamの性能/有用性

2022-05-02 19:40:42

質問

nioを使用した場合のパフォーマンス(または利点)に違いがあるかどうかを把握しようとしています。 FileChannel と通常の FileInputStream/FileOuputStream を使用して、ファイルシステムへのファイルの読み取りと書き込みを行います。私のマシンでは、どちらも同じレベルで動作していることが確認されましたし、何度も FileChannel の方が遅いです。この2つの方法の比較の詳細を教えてください。私が使用したコードは以下の通りです。 350MB . ランダムアクセスやその他の高度な機能を必要としない場合、ファイルI/OにNIOベースのクラスを使用することは良い選択でしょうか?

package trialjavaprograms;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class JavaNIOTest {
    public static void main(String[] args) throws Exception {
        useNormalIO();
        useFileChannel();
    }

    private static void useNormalIO() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        byte[] buf = new byte[64 * 1024];
        int len = 0;
        while((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        fos.flush();
        fos.close();
        is.close();
        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }

    private static void useFileChannel() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        FileInputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        FileChannel f = is.getChannel();
        FileChannel f2 = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
        long len = 0;
        while((len = f.read(buf)) != -1) {
            buf.flip();
            f2.write(buf);
            buf.clear();
        }

        f2.close();
        f.close();

        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }
}

解決方法は?

私の経験では、ファイルサイズが大きい場合、以下のようになります。 java.nio よりも高速です。 java.io . しっかり高速化。 >250%台とか。 とはいえ、私は明らかなボトルネックを排除しており、あなたのマイクロベンチマークが苦しむかもしれないことを示唆しています。 調査する可能性のある分野です。

バッファの大きさ 基本的に持っているアルゴリズムは

  • ディスクからバッファにコピー
  • バッファからディスクへのコピー

私自身の経験では、このバッファの大きさは チューニングのために。 私は、アプリケーションのある部分では4KB、別の部分では256KBに落ち着きました。 あなたのコードは、このような大きなバッファで苦しんでいるのではないでしょうか。 1KB、2KB、4KB、8KB、16KB、32KB、64KBのバッファでベンチマークを実行し、それを証明してみてください。

同じディスクに読み書きするようなjavaベンチマークを実行しないこと。

もしそうなら、あなたは本当にJavaではなく、ディスクをベンチマークしていることになります。 また、CPUがビジー状態でないのなら、何か他のボトルネックが発生している可能性がありますね。

必要なければバッファを使わないでください。

ターゲットが他のディスクやNICである場合、なぜメモリにコピーするのですか? 大きなファイルでは、発生するレイテンシは自明ではありません。

他の人が言っているように FileChannel.transferTo() または FileChannel.transferFrom() . ここでの重要な利点は、JVMがOSのDMAへのアクセス( ダイレクトメモリーアクセス ) があれば、それを使用します。 (これは実装に依存しますが、汎用CPU上の最近のSunやIBMのバージョンは問題ないです)。 データはディスクからバスへ、そして目的地へ...RAMやCPUを経由する回路をバイパスして直接送られるのです。

私が日夜取り組んでいるWebアプリは、IOが非常に重いです。 マイクロベンチマークや実世界のベンチマークもやってみたよ。 その結果は私のブログにアップされているので、見てみてください。

本番データ・環境を利用する

マイクロベンチマークは歪みやすい。 可能であれば、想定したハードウェアで、想定した負荷で、想定した通りのデータを収集するよう努力してください。

私のベンチマークが堅実で信頼できるのは、ログを収集し、負荷のかかったシステム、高性能なシステムで実施されたからです。 はありません。 私は、JVMが私のハードディスクを操作するのを激しく観察しながら、ノートブックの7200回転の2.5" SATAドライブを操作しました。

何を使っているのですか? それは重要です。