Традиционный IO против отображения памяти - PullRequest
4 голосов
/ 07 апреля 2010

Я пытаюсь проиллюстрировать студентам разницу в производительности между традиционными файлами ввода-вывода и файлами отображения памяти в 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) Не лучше ли было бы читать целые числа из ранее написанного файл и просто читать и писать целые на том же месте? Есть ли лучший способ проиллюстрировать это?

Некоторое время я ломал голову над многими из этих вещей, и я просто не могу понять всю картину ..

Ответы [ 3 ]

2 голосов
/ 08 апреля 2010

То, что я вижу в одном тесте «Поток чтения / записи»:

  • В действительности он не выполняет потоковый ввод-вывод, а ищет определенное место в файле. Это небуферизованное, поэтому все операции ввода / вывода должны выполняться с диска (другие потоки используют буферизованные операции ввода / вывода, поэтому действительно для чтения / записи в больших блоках, а затем для чтения или записи в область памяти).
  • Он ищет до конца - 4 байта, поэтому читает последний int и записывает новый int. Файл продолжает увеличиваться в длине на одну целую каждую итерацию. Это, правда, не сильно увеличивает затраты времени (но показывает, что автор этого теста либо что-то неправильно понял, либо не был осторожен).

Это объясняет очень высокую стоимость этого конкретного теста.

Вы спросили:

Не лучше ли читать инт из ранее написанного файла и просто читать и писать целые на том же место

Это то, что автор, я думаю, пытался сделать с двумя последними тестами, но это не то, что они получили. С RandomAccessFile для чтения и записи в файл в одном и том же месте вам нужно будет поставить поиск перед чтением и записью:

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

Это демонстрирует одно преимущество ввода-вывода с отображением в памяти, поскольку вы можете просто использовать один и тот же адрес памяти для доступа к одним и тем же битам файла вместо того, чтобы выполнять дополнительный поиск перед каждым вызовом.

Кстати, у вашего первого примера класса теста тоже могут быть проблемы, поскольку CHUNK_SIZE не кратно размеру блока файловой системы. Часто хорошо использовать кратные 1024 и 8192, и это показалось хорошим местом для большинства приложений (и причина, по которой Java BufferedInputStream и BufferedOutputStream использует это значение для размеров буфера по умолчанию). Операционная система должна будет прочитать дополнительные блоки, чтобы удовлетворить запросы на чтение, которые не находятся на границах блоков. Последующие чтения (потока) перечитают тот же блок, возможно, некоторые полные блоки, а затем снова дополнительные. Операции ввода-вывода с отображением в память всегда физически считывают и записывают в виде блоков, поскольку фактические операции ввода-вывода обрабатываются диспетчером памяти ОС, который использует размер страницы. Размер страницы всегда оптимизирован для точного сопоставления с блоками файлов.

В этом примере тест с отображением памяти все читает в буфер памяти, а затем записывает все обратно. Эти два теста действительно плохо написаны для сравнения этих двух случаев. memmoryMappedCopy должен читать и писать в том же размере, что и customBufferedCopy.

РЕДАКТИРОВАТЬ : Возможно, что-то еще не так с этими тестовыми классами. Из-за вашего комментария к другому ответу я снова посмотрел внимательнее на первый класс.

Метод customBufferedCopy является статическим и использует статический буфер. Для этого вида теста этот буфер должен быть определен в методе. Тогда ему не нужно было бы использовать synchronized (хотя это и не нужно в этом контексте и для этих тестов в любом случае). Этот статический метод вызывается как обычный метод, что является плохой практикой программирования (т. Е. Используйте FileCopy.customBufferedCopy(...) вместо new FileCopy().customBufferedCopy(...)).

Если бы вы на самом деле запускали этот тест из нескольких потоков, использование этого буфера было бы спорным, и эталонный тест был бы не только для файлового ввода-вывода, поэтому было бы несправедливо сравнивать результаты двух методов тестирования.

0 голосов
/ 09 апреля 2010

Спасибо за внимание к этому. Я рассмотрю первые примеры позже, а пока мой профессор попросил переписать 2 теста (Stream и mapped read / write)
Они генерируют случайные целые числа, сначала читают индекс (сгенерированный 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();
        }
    },

Так что это все еще небуферизовано ..

Второй тест я сделал следующим образом:

    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+) требуются возрасты. Я просто попробовал кое-что, но я не уверен, что я на правильном пути.

0 голосов
/ 07 апреля 2010

1) Эти вопросы звучат как вопросы, которые ваши ученики должны задавать, а не наоборот?

2) Причина, по которой используются два метода, заключается в демонстрации различных способов копирования файла. Я бы рискнул предположить, что первый метод (RamdomAccessFile) создает версию файла в ОЗУ, а затем копирует в новую версию на диске, и что второй метод (customBufferedCop) читает непосредственно с диска.

3) Я не уверен, но я думаю, что используется synchronized, чтобы гарантировать, что несколько экземпляров одного и того же класса не пишут одновременно.

4) Что касается последнего вопроса, мне нужно уйти - поэтому я надеюсь, что кто-то еще может помочь вам с этим.

Если серьезно, то это звучит как вопросы, которые преподаватель должен преподавать своим ученикам. Если у вас нет возможности самостоятельно исследовать такие простые вещи, какой пример вы подаете своим ученикам?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...