Подтвердить что ты не робот

Высокая производительность жестких дисков Java

У меня есть два (2 ГБ каждого) файла на моем жестком диске и хочу сравнить их друг с другом:

  • Копирование исходных файлов с помощью проводника Windows занимает ок. 2-4 минуты (то есть чтение и запись - на одном физическом и логическом диске).
  • Чтение с помощью java.io.FileInputStream дважды и сравнение массивов байтов по байтам на байт занимает 20 + минут.
  • java.io.BufferedInputStream buffer - 64kb, файлы читаются в кусках, а затем сравниваются.
  • Сопоставление выполняется с плотным циклом, например

    int numRead = Math.min(numRead[0], numRead[1]);
    for (int k = 0; k < numRead; k++)
    {
       if (buffer[1][k] != buffer[0][k])
       {
          return buffer[0][k] - buffer[1][k];
       }
    }
    

Что я могу сделать, чтобы ускорить это? Предполагается ли, что NIO быстрее, чем простые потоки? Является ли Java неспособным использовать технологии DMA/SATA и вместо этого выполняет некоторые медленные вызовы OS-API?

EDIT:
Спасибо за ответы. Я сделал несколько экспериментов на их основе. Как показал Андреас

Потоки

или подходы nio мало чем отличаются.
Более важным является правильный размер буфера.

Это подтверждают мои собственные эксперименты. Поскольку файлы читаются большими кусками, даже дополнительные буферы (BufferedInputStream) ничего не дают. Оптимизация сравнения возможна, и я получил лучшие результаты с 32-кратным разворачиванием, но время, проведенное в сравнении, невелико по сравнению с чтением диска, поэтому ускорение мало. Похоже, я ничего не могу сделать; - (

4b9b3361

Ответ 1

Я опробовал три разных метода сравнения двух идентичных файлов размером 3,8 ГБ с размерами буфера от 8 до 1 МБ. первый первый метод использовал только два буферизованных входных потока

второй подход использует threadpool, который читает в двух разных потоках и сравнивается в третьем. это немного повысило пропускную способность за счет использования высокопроизводительных процессоров. управление threadpool требует больших накладных расходов с этими краткосрочными задачами.

в третьем подходе используется nio, как указано laginimaineb

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

что странно, что я читаю 1 байт меньше, используя потоки. я не мог заметить ошибку.

comparing just with two streams
I was equal, even after 3684070360 bytes and reading for 704813 ms (4,98MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070360 bytes and reading for 578563 ms (6,07MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070360 bytes and reading for 515422 ms (6,82MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070360 bytes and reading for 534532 ms (6,57MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070360 bytes and reading for 422953 ms (8,31MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070360 bytes and reading for 793359 ms (4,43MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070360 bytes and reading for 746344 ms (4,71MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070360 bytes and reading for 669969 ms (5,24MB/sec * 2) with a buffer size of 1024 kB
comparing with threads
I was equal, even after 3684070359 bytes and reading for 602391 ms (5,83MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070359 bytes and reading for 523156 ms (6,72MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070359 bytes and reading for 527547 ms (6,66MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070359 bytes and reading for 276750 ms (12,69MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070359 bytes and reading for 493172 ms (7,12MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070359 bytes and reading for 696781 ms (5,04MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070359 bytes and reading for 727953 ms (4,83MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070359 bytes and reading for 741000 ms (4,74MB/sec * 2) with a buffer size of 1024 kB
comparing with nio
I was equal, even after 3684070360 bytes and reading for 661313 ms (5,31MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070360 bytes and reading for 656156 ms (5,35MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070360 bytes and reading for 491781 ms (7,14MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070360 bytes and reading for 317360 ms (11,07MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070360 bytes and reading for 643078 ms (5,46MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070360 bytes and reading for 865016 ms (4,06MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070360 bytes and reading for 716796 ms (4,90MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070360 bytes and reading for 652016 ms (5,39MB/sec * 2) with a buffer size of 1024 kB

используемый код:

import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.concurrent.*;

public class FileCompare {

    private static final int MIN_BUFFER_SIZE = 1024 * 8;
    private static final int MAX_BUFFER_SIZE = 1024 * 1024;
    private String fileName1;
    private String fileName2;
    private long start;
    private long totalbytes;

    @Before
    public void createInputStream() {
        fileName1 = "bigFile.1";
        fileName2 = "bigFile.2";
    }

    @Test
    public void compareTwoFiles() throws IOException {
        System.out.println("comparing just with two streams");
        int currentBufferSize = MIN_BUFFER_SIZE;
        while (currentBufferSize <= MAX_BUFFER_SIZE) {
            compareWithBufferSize(currentBufferSize);
            currentBufferSize *= 2;
        }
    }

    @Test
    public void compareTwoFilesFutures() 
            throws IOException, ExecutionException, InterruptedException {
        System.out.println("comparing with threads");
        int myBufferSize = MIN_BUFFER_SIZE;
        while (myBufferSize <= MAX_BUFFER_SIZE) {
            start = System.currentTimeMillis();
            totalbytes = 0;
            compareWithBufferSizeFutures(myBufferSize);
            myBufferSize *= 2;
        }
    }

    @Test
    public void compareTwoFilesNio() throws IOException {
        System.out.println("comparing with nio");
        int myBufferSize = MIN_BUFFER_SIZE;
        while (myBufferSize <= MAX_BUFFER_SIZE) {
            start = System.currentTimeMillis();
            totalbytes = 0;
            boolean wasEqual = isEqualsNio(myBufferSize);

            if (wasEqual) {
                printAfterEquals(myBufferSize);
            } else {
                Assert.fail("files were not equal");
            }

            myBufferSize *= 2;
        }

    }

    private void compareWithBufferSize(int myBufferSize) throws IOException {
        final BufferedInputStream inputStream1 =
                new BufferedInputStream(
                        new FileInputStream(new File(fileName1)),
                        myBufferSize);
        byte[] buff1 = new byte[myBufferSize];
        final BufferedInputStream inputStream2 =
                new BufferedInputStream(
                        new FileInputStream(new File(fileName2)),
                        myBufferSize);
        byte[] buff2 = new byte[myBufferSize];
        int read1;

        start = System.currentTimeMillis();
        totalbytes = 0;
        while ((read1 = inputStream1.read(buff1)) != -1) {
            totalbytes += read1;
            int read2 = inputStream2.read(buff2);
            if (read1 != read2) {
                break;
            }
            if (!Arrays.equals(buff1, buff2)) {
                break;
            }
        }
        if (read1 == -1) {
            printAfterEquals(myBufferSize);
        } else {
            Assert.fail("files were not equal");
        }
        inputStream1.close();
        inputStream2.close();
    }

    private void compareWithBufferSizeFutures(int myBufferSize)
            throws ExecutionException, InterruptedException, IOException {
        final BufferedInputStream inputStream1 =
                new BufferedInputStream(
                        new FileInputStream(
                                new File(fileName1)),
                        myBufferSize);
        final BufferedInputStream inputStream2 =
                new BufferedInputStream(
                        new FileInputStream(
                                new File(fileName2)),
                        myBufferSize);

        final boolean wasEqual = isEqualsParallel(myBufferSize, inputStream1, inputStream2);

        if (wasEqual) {
            printAfterEquals(myBufferSize);
        } else {
            Assert.fail("files were not equal");
        }
        inputStream1.close();
        inputStream2.close();
    }

    private boolean isEqualsParallel(int myBufferSize
            , final BufferedInputStream inputStream1
            , final BufferedInputStream inputStream2)
            throws InterruptedException, ExecutionException {
        final byte[] buff1Even = new byte[myBufferSize];
        final byte[] buff1Odd = new byte[myBufferSize];
        final byte[] buff2Even = new byte[myBufferSize];
        final byte[] buff2Odd = new byte[myBufferSize];
        final Callable<Integer> read1Even = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream1.read(buff1Even);
            }
        };
        final Callable<Integer> read2Even = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream2.read(buff2Even);
            }
        };
        final Callable<Integer> read1Odd = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream1.read(buff1Odd);
            }
        };
        final Callable<Integer> read2Odd = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream2.read(buff2Odd);
            }
        };
        final Callable<Boolean> oddEqualsArray = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Arrays.equals(buff1Odd, buff2Odd);
            }
        };
        final Callable<Boolean> evenEqualsArray = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Arrays.equals(buff1Even, buff2Even);
            }
        };

        ExecutorService executor = Executors.newCachedThreadPool();
        boolean isEven = true;
        Future<Integer> read1 = null;
        Future<Integer> read2 = null;
        Future<Boolean> isEqual = null;
        int lastSize = 0;
        while (true) {
            if (isEqual != null) {
                if (!isEqual.get()) {
                    return false;
                } else if (lastSize == -1) {
                    return true;
                }
            }
            if (read1 != null) {
                lastSize = read1.get();
                totalbytes += lastSize;
                final int size2 = read2.get();
                if (lastSize != size2) {
                    return false;
                }
            }
            isEven = !isEven;
            if (isEven) {
                if (read1 != null) {
                    isEqual = executor.submit(oddEqualsArray);
                }
                read1 = executor.submit(read1Even);
                read2 = executor.submit(read2Even);
            } else {
                if (read1 != null) {
                    isEqual = executor.submit(evenEqualsArray);
                }
                read1 = executor.submit(read1Odd);
                read2 = executor.submit(read2Odd);
            }
        }
    }

    private boolean isEqualsNio(int myBufferSize) throws IOException {
        FileChannel first = null, seconde = null;
        try {
            first = new FileInputStream(fileName1).getChannel();
            seconde = new FileInputStream(fileName2).getChannel();
            if (first.size() != seconde.size()) {
                return false;
            }
            ByteBuffer firstBuffer = ByteBuffer.allocateDirect(myBufferSize);
            ByteBuffer secondBuffer = ByteBuffer.allocateDirect(myBufferSize);
            int firstRead, secondRead;
            while (first.position() < first.size()) {
                firstRead = first.read(firstBuffer);
                totalbytes += firstRead;
                secondRead = seconde.read(secondBuffer);
                if (firstRead != secondRead) {
                    return false;
                }
                if (!nioBuffersEqual(firstBuffer, secondBuffer, firstRead)) {
                    return false;
                }
            }
            return true;
        } finally {
            if (first != null) {
                first.close();
            }
            if (seconde != null) {
                seconde.close();
            }
        }
    }

    private static boolean nioBuffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
        if (first.limit() != second.limit() || length > first.limit()) {
            return false;
        }
        first.rewind();
        second.rewind();
        for (int i = 0; i < length; i++) {
            if (first.get() != second.get()) {
                return false;
            }
        }
        return true;
    }

    private void printAfterEquals(int myBufferSize) {
        NumberFormat nf = new DecimalFormat("#.00");
        final long dur = System.currentTimeMillis() - start;
        double seconds = dur / 1000d;
        double megabytes = totalbytes / 1024 / 1024;
        double rate = (megabytes) / seconds;
        System.out.println("I was equal, even after " + totalbytes
                + " bytes and reading for " + dur
                + " ms (" + nf.format(rate) + "MB/sec * 2)" +
                " with a buffer size of " + myBufferSize / 1024 + " kB");
    }
}

Ответ 2

С такими большими файлами вы получите МНОГО лучшую производительность с java.nio.

Кроме того, чтение одиночных байтов с потоками java может быть очень медленным. Использование байтового массива (2-6K элементов из моего собственного опыта, ymmv, как кажется платформа/приложение) значительно улучшит вашу производительность чтения с потоками.

Ответ 3

Чтение и запись файлов с помощью Java может быть столь же быстрым. Вы можете использовать FileChannels. Что касается сравнения файлов, очевидно, это займет много времени, сравнивая байт с байтом Вот пример использования FileChannels и ByteBuffers (может быть дополнительно оптимизирован):

public static boolean compare(String firstPath, String secondPath, final int BUFFER_SIZE) throws IOException {
    FileChannel firstIn = null, secondIn = null;
    try {
        firstIn = new FileInputStream(firstPath).getChannel();
        secondIn = new FileInputStream(secondPath).getChannel();
        if (firstIn.size() != secondIn.size())
            return false;
        ByteBuffer firstBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        ByteBuffer secondBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        int firstRead, secondRead;
        while (firstIn.position() < firstIn.size()) {
            firstRead = firstIn.read(firstBuffer);
            secondRead = secondIn.read(secondBuffer);
            if (firstRead != secondRead)
                return false;
            if (!buffersEqual(firstBuffer, secondBuffer, firstRead))
                return false;
        }
        return true;
    } finally {
        if (firstIn != null) firstIn.close();
        if (secondIn != null) firstIn.close();
    }
}

private static boolean buffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
    if (first.limit() != second.limit())
        return false;
    if (length > first.limit())
        return false;
    first.rewind(); second.rewind();
    for (int i=0; i<length; i++)
        if (first.get() != second.get())
            return false;
    return true;
}

Ответ 4

Ниже приведена хорошая статья об относительных достоинствах различных способов чтения файла в java. Может быть полезно:

Как быстро читать файлы

Ответ 5

После изменения функции сравнения NIO я получаю следующие результаты.

I was equal, even after 4294967296 bytes and reading for 304594 ms (13.45MB/sec * 2) with a buffer size of 1024 kB
I was equal, even after 4294967296 bytes and reading for 225078 ms (18.20MB/sec * 2) with a buffer size of 4096 kB
I was equal, even after 4294967296 bytes and reading for 221351 ms (18.50MB/sec * 2) with a buffer size of 16384 kB

Примечание: это означает, что файлы читаются со скоростью 37 МБ/с

Запуск того же самого на более быстром диске

I was equal, even after 4294967296 bytes and reading for 178087 ms (23.00MB/sec * 2) with a buffer size of 1024 kB
I was equal, even after 4294967296 bytes and reading for 119084 ms (34.40MB/sec * 2) with a buffer size of 4096 kB
I was equal, even after 4294967296 bytes and reading for 109549 ms (37.39MB/sec * 2) with a buffer size of 16384 kB

Примечание: это означает, что файлы читаются со скоростью 74,8 МБ/с

private static boolean nioBuffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
    if (first.limit() != second.limit() || length > first.limit()) {
        return false;
    }
    first.rewind();
    second.rewind();
    int i;
    for (i = 0; i < length-7; i+=8) {
        if (first.getLong() != second.getLong()) {
            return false;
        }
    }
    for (; i < length; i++) {
        if (first.get() != second.get()) {
            return false;
        }
    }
    return true;
}

Ответ 6

Вы можете взглянуть на Suns Article для настройки ввода/вывода (хотя это уже немного устарело), ​​возможно, вы можете найти сходство между примеры там и ваш код. Также рассмотрите пакет java.nio, который содержит более быстрые элементы ввода-вывода, чем java.io. У доктора Dobbs Journal есть довольно хорошая статья о высокопроизводительный ввод-вывод с использованием java.nio.

Если да, есть дополнительные примеры и советы по настройке, которые могут помочь вам ускорить ваш код.

Кроме того, класс Arrays имеет методы сравнения массивов байтов, возможно, они также могут быть использованы для ускорения и очистки вашего петля немного.

Ответ 7

Для лучшего сравнения попробуйте скопировать сразу два файла. Жесткий диск может читать один файл гораздо эффективнее, чем читать два (поскольку голова должна двигаться вперед и назад, чтобы читать) Один из способов уменьшить это - использовать более крупные буферы, например. 16 МБ. с ByteBuffer.

С ByteBuffer вы можете сравнить по 8 байт за раз, сравнивая длинные значения с getLong()

Если ваша Java эффективна, большая часть работы находится на диске/ОС для чтения и записи, поэтому она не должна быть намного медленнее, чем с использованием любого другого языка (поскольку диск/ОС является узким местом)

Не предполагайте, что Java медленная, пока вы не определили ее не ошибку в коде.

Ответ 8

Я обнаружил, что многие статьи, связанные с этим сообщением, действительно устарели (есть и некоторые очень проницательные вещи). Есть некоторые статьи, связанные с 2001 года, и информация в лучшем случае сомнительна. Мартин Томпсон о механической симпатии написал об этом в 2011 году. Пожалуйста, обратитесь к тому, что он написал для фона и теории этого.

Я обнаружил, что NIO или нет NIO имеет очень мало общего с производительностью. Это гораздо больше о размерах ваших выходных буферов (чтение байтового массива на этом). NIO - это не волшебство, так это быстрый соус для веб-масок.

Я смог взять примеры с Мартином и использовать 1.0 OutputSream и заставить его кричать. NIO тоже очень быстрый, но самый большой индикатор - это только размер выходного буфера, независимо от того, используете ли вы NIO или нет, если, конечно, вы не используете карту NIO с памятью, это имеет значение.:)

Если вы хотите обновить авторитетную информацию об этом, см. блог Мартина:

http://mechanical-sympathy.blogspot.com/2011/12/java-sequential-io-performance.html

Если вы хотите увидеть, как NIO не делает большую часть различий (поскольку я смог писать примеры с использованием обычного ввода-вывода, которые были быстрее), см. это:

http://www.dzone.com/links/fast_java_io_nio_is_always_faster_than_fileoutput.html

Я протестировал свое предположение на новом ноутбуке с быстрым жестким диском, моим MacBook Pro с SSD, EC2 xlarge и EC2 4x большим с максимальным выходом IOPS/высокоскоростным вводом/выводом (и вскоре на большом диске NAS), поэтому он работает (есть некоторые проблемы с ним для небольших экземпляров EC2, но если вы заботитесь о производительности... собираетесь ли вы использовать небольшой экземпляр EC2?). Если вы используете реальное оборудование, в моих тестах до сих пор всегда выигрывает традиционный IO. Если вы используете high/IO EC2, то это также явный победитель. Если вы используете драйверы EC2, NIO может выиграть.

Нет никакой замены для бенчмаркинга.

В любом случае, я не эксперт, я просто сделал некоторые эмпирические тесты, используя структуру, которую написал сэр Мартин Томпсон в своем блоге.

Я сделал это на следующем шаге и использовал Files.newInputStream (из JDK 7) с помощью TransferQueue, чтобы создать рецепт для создания крита ввода-вывода Java (даже на небольшие экземпляры EC2). Рецепт можно найти в нижней части этой документации для Boon (https://github.com/RichardHightower/boon/wiki/Auto-Growable-Byte-Buffer-like-a-ByteBuilder). Это позволяет мне использовать традиционный OutputStream, но с чем-то, что хорошо работает на небольших экземплярах EC2. (Я главный автор Boon.Но я принимаю новых авторов.Оплата платится 0 $в час. Но хорошая новость в том, что я могу удвоить вашу зарплату, когда захотите.)

Мои 2 цента.

См. это, чтобы понять, почему важно TransferQueue. http://php.sabscape.com/blog/?p=557

Ключевые уроки:

  • Если вы заботитесь о производительности, никогда, никогда, никогда не используйте BufferedOutputStream.
  • NIO не всегда соответствует производительности.
  • Большее значение имеет размер буфера.
  • Рециркуляционные буферы для высокоскоростной записи имеют решающее значение.
  • GC может/будет/делает взламывание вашей производительности для высокоскоростных записей.
  • У вас должен быть механизм для повторного использования отработанных буферов.

Ответ 9

DMA/SATA являются аппаратными/низкоуровневыми технологиями и не видны ни одному языку программирования.

Для ввода/вывода с отображением памяти вы должны использовать java.nio, я полагаю.

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

Ответ 10

Попробуйте настроить буфер на входной поток до нескольких мегабайт.