ストリームIO,NIO,メモリマップ読み込み速度テスト簡単分析

21509 ワード

テスト環境


cpu: intel® core™ i 7-8750 Hメモリ:16 Gハードディスク:512 SSDテストファイル:四つのファイルのzipパッケージ、圧縮後のサイズ1.73 GB

テスト結果


フローIO読み出しサンプルコード

        final int BUFFER_SIZE = 1024;
        int i = 0;

        byte[] bytes = new byte[BUFFER_SIZE];
        File file2 = new File(filePath);
        FileInputStream in2 = new FileInputStream(file2);

        long begin3 = System.currentTimeMillis();
        while (in2.read(bytes) > 0){
            i++;
        }
        in2.close();
        long end3 = System.currentTimeMillis();
        System.out.println("count is:" + i);
        System.out.println("time is:" + (end3 - begin3));

NIO読み出しサンプルコード

        final int BUFFER_SIZE = 1024;
        int i = 0;
        File file = new File(filePath);
        FileInputStream in = new FileInputStream(file);
        FileChannel channel2 = in.getChannel();
        ByteBuffer buff = ByteBuffer.allocate(BUFFER_SIZE);

        long begin2 = System.currentTimeMillis();
        while (channel2.read(buff) != -1) {
           i++;
            buff.flip();
            buff.clear();
        }
        channel2.close();
        long end2 = System.currentTimeMillis();
        System.out.println("count is:" + i);
        System.out.println("time is:" + (end2 - begin2));

メモリマッピング読み込みサンプルコード

        final int BUFFER_SIZE = 1024;
        int i = 0;

        byte[] bytes = new byte[BUFFER_SIZE];

        RandomAccessFile memoryMappedFile = new RandomAccessFile(filePath, "rw");
        FileChannel channel = memoryMappedFile.getChannel();
        final long len = channel.size();
        long begin = System.currentTimeMillis();

        MappedByteBuffer out = channel.map(FileChannel.MapMode.READ_WRITE, 0, len);
        for (int offset = 0; offset < len; offset += BUFFER_SIZE) {
            i++;
            if (len - offset >= BUFFER_SIZE) {
                out.get(bytes);
            } else {
                out.get(bytes,0,(int) len - offset);
            }
        }
        memoryMappedFile.close();
        long end = System.currentTimeMillis();
        System.out.println("count is:" + i);
        System.out.println("time is: " + (end - begin));

テスト結果


それぞれのテストバッファは1 K,1 M,10 Mである.ケースごとに3回実行
buffer size
フローIO
NIO
メモリマッピング
1K
count is:1818681 time is:4717
count is:1818681 time is:4873
count is:1818681 time is:736
1K
count is:1818681 time is:4673
count is:1818681 time is:4779
count is:1818681 time is:733
1K
count is:1818681 time is:4700
count is:1818681 time is:4731
count is:1818681 time is:728
1M
count is:1777 time is:1139
count is:1777 time is:459
count is:1777 time is:757
1M
count is:1777 time is:1148
count is:1777 time is:454
count is:1777 time is:744
1M
count is:1777 time is:1103
count is:1777 time is:461
count is:1777 time is:764
10M
count is:178 time is:1293
count is:178 time is:964
count is:178 time is:830
10M
count is:178 time is:1263
count is:178 time is:903
count is:178 time is:845
10M
count is:178 time is:1241
count is:178 time is:896
count is:178 time is:840

結論

  • 表から、全体的に従来のフローIO読み取り速度が最も遅いことがわかる.
  • の3つの方式の速度が最も速い場合はいずれも1 Mであり、すなわちバッファが大きいほど速くない.
  • バッファが小さい場合、フローIOとNIOはいずれも遅く耐えられず、読み取り速度の差は大きくない.
  • メモリマッピング方式は、バッファサイズにかかわらず読み取り速度が相対的に安定している.
  • パラメータが適切である場合、NIO読み取り速度が最も速い.

  • 疑問

  • バッファが小さい場合、なぜNIOの読み取り速度がこんなに遅いのですか?
  • バッファサイズは、メモリマッピングに与える影響が小さいのはなぜですか?

  • に答える


    疑問に対してNIO readメソッドのコードをいくつか考えさせてください.read関数のソースコードを開くと見つかります.sun.nio.ch.IOUtil#read(java.io.FileDescriptor, java.nio.ByteBuffer, long, sun.nio.ch.NativeDispatcher)
     static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
            if (var1.isReadOnly()) {
                throw new IllegalArgumentException("Read-only buffer");
            } else if (var1 instanceof DirectBuffer) {
                return readIntoNativeBuffer(var0, var1, var2, var4);
            } else {
                ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());
    
                int var7;
                try {
                    int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
                    var5.flip();
                    if (var6 > 0) {
                        var1.put(var5);
                    }
    
                    var7 = var6;
                } finally {
                    Util.offerFirstTemporaryDirectBuffer(var5);
                }
    
                return var7;
            }
        }
    

    ここから、バッファが直接メモリJDKでない場合、同じサイズの一時的な直接メモリ読み出しファイルが作成され、一時的な直接メモリからコピーされることがわかります.したがって、コードを変更するには、直接メモリをバッファとして使用して再テストします.
    //  
    ByteBuffer buff = ByteBuffer.allocateDirect(BUFFER_SIZE);
    

    buffer size
    NIO+ダイレクトメモリ
    NIO
    1K
    count is:1818681 time is:4720
    count is:1818681 time is:4873
    1K
    count is:1818681 time is:4620
    count is:1818681 time is:4873
    1K
    count is:1818681 time is:4622
    count is:1818681 time is:4873
    1M
    count is:1777 time is:314
    count is:1777 time is:454
    1M
    count is:1777 time is:330
    count is:1777 time is:454
    1M
    count is:1777 time is:310
    count is:1777 time is:454
    10M
    count is:178 time is:514
    count is:178 time is:896
    10M
    count is:178 time is:510
    count is:178 time is:896
    10M
    count is:178 time is:510
    count is:178 time is:896
    テスト結果から,直接メモリをバッファとして使用すると読み取り速度は一定に向上するが,バッファが小さい場合でも遅いのは,自身の読み取り方式の問題であるはずである.疑問2メモリマッピングでは、ファイルをメモリに完全にロードすることはできませんが、プリフェッチがメモリ間でコピーされているため、速度が速く、メモリマッピングを使用すると、カーネル領域からユーザー領域へのデータのコピーを節約するだけで、他の読み取り方法は1回だけメモリコピーが遅くなったのはなぜか説明できません.