4

従来の IO と Java のメモリ マップ ファイルのパフォーマンスの違いを学生に説明しようとしています。インターネットのどこかで例を見つけましたが、すべてが明確であるとは限りません。すべての手順が必要だとは思いません。私はあちこちでそれについて多くのことを読みましたが、どちらの正しい実装についても確信が持てません。

私が理解しようとするコードは次のとおりです。

public class FileCopy{
    public static void main(String args[]){
        if (args.length < 1){
            System.out.println(" Wrong usage!");
            System.out.println(" Correct usage is : java FileCopy <large file with full path>");
            System.exit(0);
        }


        String inFileName = args[0];
        File inFile = new File(inFileName);

        if (inFile.exists() != true){
            System.out.println(inFileName + " does not exist!");
            System.exit(0);
        }

        try{
            new FileCopy().memoryMappedCopy(inFileName, inFileName+".new" );
            new FileCopy().customBufferedCopy(inFileName, inFileName+".new1");
        }catch(FileNotFoundException fne){
            fne.printStackTrace();
        }catch(IOException ioe){
            ioe.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }


    }

    public void memoryMappedCopy(String fromFile, String toFile ) throws Exception{
        long timeIn = new Date().getTime();
        // read input file
        RandomAccessFile rafIn = new RandomAccessFile(fromFile, "rw");
        FileChannel fcIn = rafIn.getChannel();
        ByteBuffer byteBuffIn = fcIn.map(FileChannel.MapMode.READ_WRITE, 0,(int) fcIn.size());
        fcIn.read(byteBuffIn);
        byteBuffIn.flip();

        RandomAccessFile rafOut = new RandomAccessFile(toFile, "rw");
        FileChannel fcOut = rafOut.getChannel();

        ByteBuffer writeMap = fcOut.map(FileChannel.MapMode.READ_WRITE,0,(int) fcIn.size());

        writeMap.put(byteBuffIn);   

        long timeOut = new Date().getTime();
        System.out.println("Memory mapped copy Time for a file of size :" + (int) fcIn.size() +" is "+(timeOut-timeIn));
        fcOut.close();
        fcIn.close();
    }


    static final int CHUNK_SIZE = 100000;
    static final char[] inChars = new char[CHUNK_SIZE];

    public static void customBufferedCopy(String fromFile, String toFile) throws IOException{
        long timeIn = new Date().getTime();

        Reader in = new FileReader(fromFile);
        Writer out = new FileWriter(toFile);
        while (true) {
            synchronized (inChars) {
                int amountRead = in.read(inChars);
                if (amountRead == -1) {
                    break;
                }
                out.write(inChars, 0, amountRead);
            }
        }
        long timeOut = new Date().getTime();
        System.out.println("Custom buffered copy Time for a file of size :" + (int) new File(fromFile).length() +" is "+(timeOut-timeIn));
        in.close();
        out.close();
    }
}

正確にいつ使用する必要がありますRandomAccessFileか?ここでは、ファイルの読み取りと書き込みに使用されますmemoryMappedCopyが、実際にはファイルをコピーするだけで十分ですか? それともメモリマッピングの一部ですか?

ではcustomBufferedCopy、なぜsynchronizedここで が使用されているのですか?

また、2 つの間のパフォーマンスをテストする必要がある別の例も見つけました。

public class MappedIO {
    private static int numOfInts = 4000000;
    private static int numOfUbuffInts = 200000;
    private abstract static class Tester {
        private String name;
        public Tester(String name) { this.name = name; }
        public long runTest() {
            System.out.print(name + ": ");
            try {
                long startTime = System.currentTimeMillis();
                test();
                long endTime = System.currentTimeMillis();
                return (endTime - startTime);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        public abstract void test() throws IOException;
    }
    private static Tester[] tests = { 
        new Tester("Stream Write") {
            public void test() throws IOException {
                DataOutputStream dos = new DataOutputStream(
                        new BufferedOutputStream(
                                new FileOutputStream(new File("temp.tmp"))));
                for(int i = 0; i < numOfInts; i++)
                    dos.writeInt(i);
                dos.close();
            }
        }, 
        new Tester("Mapped Write") {
            public void test() throws IOException {
                FileChannel fc = 
                    new RandomAccessFile("temp.tmp", "rw")
                .getChannel();
                IntBuffer ib = fc.map(
                        FileChannel.MapMode.READ_WRITE, 0, fc.size())
                        .asIntBuffer();
                for(int i = 0; i < numOfInts; i++)
                    ib.put(i);
                fc.close();
            }
        }, 
        new Tester("Stream Read") {
            public void test() throws IOException {
                DataInputStream dis = new DataInputStream(
                        new BufferedInputStream(
                                new FileInputStream("temp.tmp")));
                for(int i = 0; i < numOfInts; i++)
                    dis.readInt();
                dis.close();
            }
        }, 
        new Tester("Mapped Read") {
            public void test() throws IOException {
                FileChannel fc = new FileInputStream(
                        new File("temp.tmp")).getChannel();
                IntBuffer ib = fc.map(
                        FileChannel.MapMode.READ_ONLY, 0, fc.size())
                        .asIntBuffer();
                while(ib.hasRemaining())
                    ib.get();
                fc.close();
            }
        }, 
        new Tester("Stream Read/Write") {
            public void test() throws IOException {
                RandomAccessFile raf = new RandomAccessFile(
                        new File("temp.tmp"), "rw");
                raf.writeInt(1);
                for(int i = 0; i < numOfUbuffInts; i++) {
                    raf.seek(raf.length() - 4);
                    raf.writeInt(raf.readInt());
                }
                raf.close();
            }
        }, 
        new Tester("Mapped Read/Write") {
            public void test() throws IOException {
                FileChannel fc = new RandomAccessFile(
                        new File("temp.tmp"), "rw").getChannel();
                IntBuffer ib = fc.map(
                        FileChannel.MapMode.READ_WRITE, 0, fc.size())
                        .asIntBuffer();
                ib.put(0);
                for(int i = 1; i < numOfUbuffInts; i++)
                    ib.put(ib.get(i - 1));
                fc.close();
            }
        }
    };
    public static void main(String[] args) {
        for(int i = 0; i < tests.length; i++)
            System.out.println(tests[i].runTest());
    }
}

多かれ少なかれ何が起こっているのかがわかります。出力は次のようになります。

Stream Write: 653
Mapped Write: 51
Stream Read: 651
Mapped Read: 40
Stream Read/Write: 14481
Mapped Read/Write: 6

ストリームの読み取り/書き込みを信じられないほど長くしている理由は何ですか? そして、読み取り/書き込みテストとして、同じ整数を何度も読み取るのは少し無意味に見えます (で何が起こっているかをよく理解している場合Stream Read/Write) 以前に書き込まれたファイルから int を読み取り、単に同じ場所でintを読み書きしますか? それを説明するより良い方法はありますか?

私はしばらくの間、これらの多くのことについて頭を悩ませてきましたが、全体像を把握することはできません..

4

3 に答える 3

2

1 つのベンチマーク「Stream Read/Write」で見られるのは次のとおりです。

  • 実際にはストリーム I/O は行いませんが、ファイル内の特定の場所をシークします。これはバッファリングされていないため、すべての I/O はディスクから完了する必要があります (他のストリームはバッファリングされた I/O を使用しているため、実際には大きなブロックで読み取り/書き込みを行い、次に int がメモリ領域から読み取られるか、メモリ領域に書き込まれます)。
  • 最後までシークしています-4バイトなので、最後のintを読み取り、新しいintを書き込みます。ファイルは、反復ごとに 1 int ずつ長さが増加し続けます。ただし、これによって時間コストが大幅に増加することはありません (ただし、そのベンチマークの作成者が何かを誤解しているか、注意を払っていなかったことを示しています)。

これは、その特定のベンチマークのコストが非常に高いことを説明しています。

あなたは尋ねました:

以前に書き込まれたファイルから int を読み取り、同じ場所で int を読み書きする方がよいのではないでしょうか?

これは、著者が最後の 2 つのベンチマークでやろうとしていたことだと思いますが、それは彼らが得たものではありません。ファイル内の同じ場所を読み書きするにRandomAccessFileは、読み取りと書き込みの前にシークを配置する必要があります。

raf.seek(raf.length() - 4);
int val = raf.readInt();
raf.seek(raf.length() - 4);
raf.writeInt(val);

これは、すべての呼び出しの前に追加のシークを行う代わりに、同じメモリ アドレスを使用してファイルの同じビットにアクセスできるため、メモリ マップド I/O の利点の 1 つを示しています。

ところで、最初のベンチマーク サンプル クラスでもCHUNK_SIZE、ファイル システム ブロック サイズの偶数倍ではないため、問題が発生する可能性があります。多くの場合、1024 の倍数を使用するのが適切であり、8192 はほとんどのアプリケーションで適切なスイート スポットとして示されています (Java がその値をデフォルトのバッファ サイズに使用する理由) BufferedInputStreamBufferedOutputStreamOS は、ブロック境界上にない読み取り要求を満たすために、余分なブロックを読み取る必要があります。(ストリームの) 後続の読み取りは、同じブロック、おそらくいくつかの完全なブロックを再読み取りし、次に余分なブロックを再度読み取ります。メモリ マップド I/O は、実際の I/O がそのページ サイズを使用する OS メモリ マネージャーによって処理されるため、常にブロック単位で物理的に読み書きします。ページ サイズは常に最適化され、ファイル ブロックに適切にマップされます。

その例では、メモリ マップド テストはすべてをメモリ バッファに読み込んでから、すべて書き戻します。これら 2 つのテストは、これら 2 つのケースを比較するために適切に作成されていません。memmoryMappedCopyと同じチャンク サイズで読み書きする必要がありますcustomBufferedCopy

EDIT:これらのテストクラスにはさらに多くの問題があるかもしれません。他の回答に対するあなたのコメントのために、私は最初のクラスをもう一度注意深く見ました。

メソッドcustomBufferedCopyは静的で、静的バッファーを使用します。この種のテストでは、バッファをメソッド内で定義する必要があります。その後、使用する必要はsynchronizedありません (ただし、このコンテキストおよびこれらのテストでは必要ありません)。この静的メソッドは通常のメソッドとして呼び出されますが、これは悪いプログラミング手法です (つまり、FileCopy.customBufferedCopy(...)の代わりに使用しますnew FileCopy().customBufferedCopy(...))。

実際に複数のスレッドからこのテストを実行した場合、そのバッファの使用は論争の的となり、ベンチマークはファイル I/O だけに関するものではないため、2 つのテスト方法の結果を比較するのは公平ではありません。

于 2010-04-07T21:24:16.983 に答える
0

1)これらは、生徒が尋ねるべき質問のように聞こえます-その逆ではありませんか?

2)2つの方法を使用する理由は、ファイルをコピーするさまざまな方法を示すためです。最初のメソッド(RamdomAccessFile)がRAMにファイルのバージョンを作成してから、ディスク上の新しいバージョンにコピーし、2番目のメソッド(customBufferedCop)がドライブから直接読み取ると推測するのは危険です。

3)わかりませんが、同期は、同じクラスの複数のインスタンスが同時に書き込みを行わないようにするために使用されていると思います。

4)最後の質問については、行かなくてはなりません。他の誰かがそれを手伝ってくれることを願っています。

真面目な話ですが、これらは家庭教師が生徒に教えるべき質問のように聞こえます。このような簡単なことを自分で研究する能力がない場合、どのような例を生徒に設定していますか?</ rant>

于 2010-04-07T16:01:37.017 に答える
0

ご覧いただきありがとうございます。後で最初の例を見ていきます。今のところ、私の教授は 2 つのテスト (ストリームとマップされた読み取り/書き込み) を書き直すように依頼しました。
それらはランダムな int を生成し、最初にインデックス (生成された int) を読み取り、このインデックスで int かどうかを確認します。は生成された int と等しく、等しくない場合は、生成された int がそのインデックスに書き込まれます。彼は、これによりより良いテストが可能になり、 をより活用できると考えましたRandomAccessFile

ただし、いくつかの問題があります。まず、を使用しているときに読み取り/書き込みストリームでバッファーを使用する方法がわかりません。配列を使用するバッファーRandomAccessFileについて多くのことを見つけましたが、正しく使用する方法がわかりません。 このテストのためのこれまでの私のコード:byte[]

    new Tester("Stream Read/Write") {
        public void test() throws IOException {
            RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"), "rw");
            raf.seek(numOfUbuffInts*4);
            raf.writeInt(numOfUbuffInts);
            for (int i = 0; i < numOfUbuffInts; i++) {
                int getal = (int) (1 + Math.random() * numOfUbuffInts);
                raf.seek(getal*4);
                if (raf.readInt() != getal) {
                    raf.seek(getal*4);
                    raf.writeInt(getal);
                }
            }
            raf.close();
        }
    },

したがって、これはまだバッファリングされていません..

私が次のように行った2番目のテスト:

    new Tester("Mapped Read/Write") {
        public void test() throws IOException {
            RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"), "rw");
            raf.seek(numOfUbuffInts*4);
            raf.writeInt(numOfUbuffInts);
            FileChannel fc = raf.getChannel();
            IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer();

            for(int i = 1; i < numOfUbuffInts; i++) {
                int getal = (int) (1 + Math.random() * numOfUbuffInts);
                if (ib.get(getal) != getal) {
                    ib.put(getal, getal);
                }
            }
            fc.close();
        }
    }

少数の場合はnumOfUbuffInts高速に見えますが、多数 (20 000 000 以上) の場合は時間がかかります。いくつか試してみましたが、正しい軌道に乗っているかどうかはわかりません。

于 2010-04-09T09:50:50.047 に答える