Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > RandomAccessFile Скорость записи


Автор: Mirkes 19.3.2014, 19:44
Мне нужен быстрый доступ к отдельным значениям в большом бинарном файле. Решил использовать RandomAccessFile.
Тут же возникла проблема - запись файла из старого формата в новый работает с потрясающей скоростью. Вот полный текст перекодировки:
Код

            for (int row = 0; row < nRow; row++) {
                for (int col = 0; col < nCol; col++) {
                    d = getData(noData, line);
                    if (d == Double.POSITIVE_INFINITY) {
                        pb.stop();
                        return;
                    }
                    raf.writeDouble(d);
                    //Define min and max!
                    if (!Double.isNaN(d)){
                        if (d<minV)
                            minV=d;
                        if (d>maxV)
                            maxV=d;
                    }
                }
            }


Файл большой nRow=8082, nCol=14090. Это спутниковый снимок в некоем странном текстовом формате.
В методе getData происходит чтение другого, текстового, файла и конвертация в числовую форму.
pb не работает с файлами.
В том виде, в котором программа приведена выше время обработки одного файла 38 минут
Если закомментировать строку с raf то время работы 19 секунд.
Запись производится подряд. Конечно, пишется 869М, но не с такой же тормознутостью.
Или это свойство RandomAccessFile?
К сожалению после завершения записи файла мне надо дописать пару значений в начало файла, так что RandomAccessFile вроде по существу.

Что посоветуете? Как ускорить работу?

Автор: Pawl 19.3.2014, 21:31
Цитата(Mirkes @  19.3.2014,  19:44 Найти цитируемый пост)
Что посоветуете?

Цитата

From the docs, rws mode means:
Open for reading and writing, as with "rw", and also require that every update to the file's content or metadata be written synchronously to the underlying storage device.
It stands to reason that forcing the file's content to be written to the underlying device would be significantly slower than the other methods, which probably allow the VM/OS to cache the writes.

Не знаю, будет ли так проще - вы можете записать перекодированные данные в один временный файл, пару значений - в другой, а затем считывать из этих файлов данные и писать их в итоговый файл. Есть в пакете io класс SequenceInputStream, он может читать из нескольких файлов по очереди. Понятно, так потребуется больше места на диске, но скорость будет выше.

Автор: Mirkes 19.3.2014, 22:02
To Pawl Спасибо, приблизительно это и реализовал.
Фактически я сделал запись в DataOutputStream а потом переоткрыл его как RandomAccessFile и дописал оставшиеся два значения. Открывал как "rw". Из документации мне показалось, что это быстрее чем rws и rwd.
В результате замены типа выходного потока время уменьшилось до 6.25 минуты, что уже приемлемо (нужно сконвертировать 12 файлов).

Однако мне все равно не понятно, почему при файле открытом как 
Код

            raf = new RandomAccessFile(fNameOut, "rw");


запись идет в 5 раз медленнее чем в файле, открытом как
Код

            DataOutputStream dos = new DataOutputStream(new FileOutputStream(fNameOut));

 Кстати только что заметил, что и ПОСЛЕДОВАТЕЛЬНОЕ чтение из файла открытого
Код

            raf = new RandomAccessFile(fNameOut, "r");

идет жутко медленно. Гораздо медленнее чем из текстового файла со все его парсингом и т.п. Засек точное время. Даже с заменой способа чтения на DataInputStream чтение всего файла заняло около 10 минут. 

Мне просто интересно, это принципиальная особенность файлов прямого доступа?
Или так сильно тормозит приведение каждого double к некоему переносимому стандарту java при записи и соответственно обратное преобразование при чтении?

Попробую сформулировать задачу иначе.
Есть большой текстовый файл (14000 столбцов и 8000 строк, что-то чуть больше 100 миллионов значений). Таких файлов 12.
К файлу есть два варианта запроса.
1. Прочитать все данные для формирования картинки на экране. Нужно пару раз на каждый файл. В принципе если плюнуть на все и просто парсить текстовый файл то это займет около 1 минуты. Не фонтан, но терпимо.
2. Указать значение с определенными координатами. Порядка 30,000 запросов на файл при условии, что повторных обработок не будет.

Ограничение на хранение массива в памяти очевидно. На массив нужно что-то около 800М памяти.
Исходя из второй задачи я предполагал, что конвертация в бинарный файл даст приличный выигрыш при дальнейших запросах, однако получается, что для создания картинки точно лучше читать прямо текстовый файл (1 минута против 10 из бинарного).
К сожалению пока не закончил вторую часть и не могу сказать сколько займет выполнение тех 30,000 запросов по координатам.

Как наиболее организовать быструю работу с файлом?
Может без интерфейсов DataOutput и DataInput работа с файлом пойдет быстрее? Переносить конвертированные файлы я не собираюсь.

Таким образом окончательная формулировка вопроса:
Какой тип работы с файлами Вы порекомендуете для этой задачи?

Автор: LSD 20.3.2014, 11:29
Я бы рекомендовал memory mapped file.
Код

        RandomAccessFile raf = new RandomAccessFile("file", "rw");
        MappedByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0L, raf.length());
        DoubleBuffer doubleBuffer = buffer.asDoubleBuffer();
        // work with file
        buffer.force();
        raf.close();

Автор: Mirkes 20.3.2014, 12:35
Цитата(LSD @  20.3.2014,  11:29 Найти цитируемый пост)
Я бы рекомендовал memory mapped file.

Спасибо. Попробую.

Попробовал и ничего не понял.
Вот код
Код

            raf = new RandomAccessFile(fNameOut, "rw");
            raf.setLength(((long)(HEAD+nCol*nRow))*DBL_SIZE);
            MappedByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0L, raf.length());
            DoubleBuffer doubleBuffer = buffer.asDoubleBuffer();

размер файла 911 003 096 байт. Файл создан именно такого размера.

В ответ получил
Код

java.io.IOException: Map failed
    at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:748)
    at geodata.GISLayer.<init>(GISLayer.java:102)
    at client.Geologic$1$1.run(Geologic.java:233)
    at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.OutOfMemoryError: Map failed
    at sun.nio.ch.FileChannelImpl.map0(Native Method)
    at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:745)
    ... 3 more

Программа запускалась с опцией
-Xmx1600m

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

Может нужно указать еще какие-то опции для взятия памяти?

Автор: LSD 21.3.2014, 11:12
Для memory mapped file не требуется памяти в полном объеме. Поэтому размер хипа как раз не важен.
Я думаю скорее всего закончилось адресное пространство процесса, и файл просто некуда мапить. У тебя ОС и JVM какие 32/64 битные?

Автор: Mirkes 21.3.2014, 11:39
ОС Windows 7 Enterprice SP1 64bit
RAM 8G

IDE JDeveloper 11.1.2.4
Java™ Platform    1.6.0_24

JRE 1.5.0_45   32bit

Забавно, JDK и JRE разные. JRE обновляется автоматически, а JDK видимо нужно обновлять вручную

Автор: LSD 21.3.2014, 12:57
Цитата(Mirkes @  21.3.2014,  12:39 Найти цитируемый пост)
32bit

У 32-х битного процесса адресное пространство 2Gb. У тебя файл 900Mb, да еще и куча 1600Mb, явно больше 2Gb.

Автор: Mirkes 21.3.2014, 13:44
Если я правильно понял, то воспользоваться отображенными файлами я смогу только перейдя на 64bit-ую Java?

Спасибо за помощь.

Автор: LSD 21.3.2014, 17:07
Цитата(Mirkes @  21.3.2014,  14:44 Найти цитируемый пост)
Если я правильно понял, то воспользоваться отображенными файлами я смогу только перейдя на 64bit-ую Java?

Нет, просто надо понимать, что чтобы использовать гигабайтный MMF сама JVM (куча+служебные области) должна занимать менее гигабайта. Простейший тест спокойно мапит гигабайтный файл, вопрос сколько у тебя в конкретном случае остается памяти.

Автор: Mirkes 21.3.2014, 17:35
Собственно это я и имел в виду. Поскольку я отображаю сопоставимые по размеру картинки, то памяти на maping файла уже не остается. Но картинки мне нужны позарез, так что придется смириться с медленной работой файла или переходить на 64 бита.

Еще раз спасибо.

Автор: Pawl 27.3.2014, 12:48
Пардон, что пишу в закрытую тему, нашел способ добавить в начало файла данные при помощи java.nio. Если вам интересно, можете попробовать, протестировать скорость и т. д. smile 
Код

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.Files.newByteChannel;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;

public class Inserter {
    public static void main(String... args) {
        String s = "I was here!\n";
        byte data[] = s.getBytes();
        ByteBuffer out = ByteBuffer.wrap(data);
        Path file = Paths.get("BrasilOpen.txt");
        try (SeekableByteChannel fc = (newByteChannel(file, READ, WRITE))) {
            // Write "I was here!" at the beginning of the file.
            fc.position(0);
            while (out.hasRemaining())
                fc.write(out);
        } catch (IOException x) {
            System.out.println("I/O Exception: " + x);
        }
    }
}

Автор: Mirkes 27.3.2014, 13:18
Цитата(Pawl @  27.3.2014,  12:48 Найти цитируемый пост)
Пардон, что пишу в закрытую тему, нашел способ добавить в начало файла данные при помощи java.nio. Если вам интересно, можете попробовать, протестировать скорость и т. д.  

Спасибо.
На самом деле это интересно, но свою проблему я решил. Не быстро, но решил.
Основной ступор был в том, что поднять на мозги весь файл было слишком накладно. А когда я пробовал еще что-то делать получал переполнение памяти. Посему я и хотел получить прямой доступ. А прямой доступ без отображения в память оказался очень медленным. А на отображение в память - нет места. И получил замкнутый круг.
На самом деле я решил достаточно просто - сначала записал без прямого доступа положив на место пока неизвестных параметров заглушки, потом переоткрыл файл с прямым доступом и записал вычисленные параметры на место.
Далее использовал прямой доступ.
Когда появится время (если появится) попробую поэкспериментировать с разными типами файлового ввода=вывода и их скоростью.
Спасибо за совет.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)