Производительность / стабильность файла Memory Mapped - Native или MappedByteBuffer - по сравнению с обычным FileOutputStream - PullRequest
11 голосов
/ 11 февраля 2009

Я поддерживаю устаревшее приложение Java, которое использует плоские файлы (простой текст) для сохранения. Из-за характера приложения размер этих файлов может достигать 100 МБ в день, и часто ограничивающим фактором в производительности приложения является ввод-вывод файла. В настоящее время приложение использует обычный файл 'java.io.FileOutputStream для записи данных на диск.

В последнее время несколько разработчиков утверждают, что использование отображенных в память файлов, реализованных в собственном коде (C / C ++) и доступ к которым осуществляется через JNI, обеспечит большую производительность. Однако FileOutputStream уже использует нативные методы для своих основных методов (например, write (byte [])), поэтому он выглядит ненадежным без жестких данных или, по крайней мере, случайных свидетельств.

У меня есть несколько вопросов по этому вопросу:

  1. Действительно ли это утверждение верно? Будут ли в памяти отображены файлы всегда обеспечить более быстрый ввод-вывод по сравнению с Java FileOutputStream

  2. Имеет ли класс MappedByteBuffer доступ из FileChannel предоставить та же функциональность, что и родная Доступ к библиотеке отображаемых файлов памяти через JNI? Что такое MappedByteBuffer не хватает того, что может привести вас к использованию Решение JNI?

  3. Каковы риски использования сопоставленные с памятью файлы для дискового ввода-вывода в производстве приложение? То есть приложения которые имеют непрерывное время работы с минимальные перезагрузки (раз в месяц, не более). Реальные анекдоты от производства приложения (Java или другие) предпочтительным.

Вопрос № 3 важен - я мог бы ответить на этот вопрос сам частично , написав «игрушечное» приложение, которое проверяет IO, используя различные опции, описанные выше, но, отправляя сообщение SO, я надеюсь на реальные анекдоты / данные для пережевывания.

[РЕДАКТИРОВАТЬ] Уточнение - каждый день работы приложение создает несколько файлов размером от 100 МБ до 1 гигабайта. В общей сложности приложение может записывать несколько гигабайт данных в день.

Ответы [ 7 ]

5 голосов
/ 11 февраля 2009

Операции ввода-вывода с отображением памяти не позволят вашим дискам работать быстрее (!). Для линейного доступа это кажется немного бессмысленным.

Отображенный буфер NIO - это реальная вещь (обычное предостережение о любой разумной реализации).

Как и в других буферах прямого назначения NIO, буферы не являются обычной памятью и не будут получать GCed так же эффективно. Если вы создадите много из них, вы можете обнаружить, что у вас не хватает памяти / адресного пространства без кучи Java. Это очевидно беспокоит долго работающие процессы.

4 голосов
/ 11 февраля 2009

Возможно, вы сможете немного ускорить процесс, изучив, как ваши данные буферизуются во время записи. Это, как правило, зависит от конкретного приложения, так как вам потребуется представление об ожидаемых шаблонах записи данных. Если важна согласованность данных, здесь будут компромиссы.

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

Если вы уверены, что вам действительно нужна более высокая производительность ввода-вывода - или просто производительность O в вашем случае, я бы посмотрел на аппаратное решение, такое как настроенный дисковый массив. Использование большего количества оборудования для решения этой проблемы часто оказывается экономически более эффективным с точки зрения бизнеса, чем затраты времени на оптимизацию программного обеспечения. Он также обычно быстрее внедряется и более надежен.

В целом, существует много подводных камней в оптимизации программного обеспечения. Вы будете вводить новые типы проблем для вашего приложения. Вы можете столкнуться с проблемами памяти / сбоя GC, которые приведут к большему количеству обслуживания / настройки. Хуже всего то, что многие из этих проблем будет сложно протестировать перед началом эксплуатации.

Если бы это было мое приложение, я бы, вероятно, придерживался FileOutputStream с некоторой, возможно, настроенной буферизацией. После этого я бы использовал проверенное временем решение, добавив в него больше оборудования.

2 голосов
/ 11 февраля 2009

По моему опыту, отображенные в память файлы работают НАМНОГО лучше, чем простой доступ к файлам как в режиме реального времени, так и в постоянном использовании. Я работал в основном с C ++ в Windows, но производительность Linux схожа, и вы все равно планируете использовать JNI, поэтому я думаю, что это относится к вашей проблеме.

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

В другом проекте мне пришлось писать многоадресные сетевые приложения. Данные были отправлены в рандомизированном порядке, чтобы минимизировать влияние последовательной потери пакетов (в сочетании с FEC и схемами блокировки). Более того, данные могут значительно превышать адресное пространство (видеофайлы размером более 2 ГБ), поэтому о распределении памяти не может быть и речи. На стороне сервера файловые секции отображались в памяти по требованию, и сетевой уровень непосредственно выбирал данные из этих представлений; как следствие, использование памяти было очень низким. На стороне получателя не было никакого способа предсказать порядок, в котором были получены пакеты, поэтому он должен поддерживать ограниченное количество активных представлений в целевом файле, и данные были скопированы непосредственно в эти представления. Когда пакет должен был быть помещен в не отображенную область, самое старое представление было не отображено (и в конечном итоге записано в файл системой) и заменено новым представлением в области назначения. Производительность была выдающейся, особенно потому, что система отлично справилась с фиксацией данных в качестве фоновой задачи, и ограничения в реальном времени были легко выполнены.

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

Надеюсь, это поможет.

1 голос
/ 03 декабря 2012

Я провел исследование , где сравнил производительность записи с необработанным ByteBuffer и производительность записи с MappedByteBuffer. Файлы с отображением в памяти поддерживаются ОС, и их задержка записи очень хорошая, как вы можете видеть из моих тестов. Выполнение синхронной записи через FileChannel происходит примерно в 20 раз медленнее, и поэтому люди все время ведут асинхронную запись в журнал. В своем исследовании я также привожу пример того, как реализовать асинхронное ведение журнала через очередь без блокировки и без мусора для максимальной производительности, очень близкой к исходной ByteBuffer.

1 голос
/ 11 февраля 2009

Что касается пункта 3 - если машина выходит из строя и есть какие-либо страницы, которые не были сброшены на диск, то они теряются. Другая вещь - это трата адресного пространства - отображение файла в память занимает адресное пространство (и требует смежной области), и на 32-битных машинах оно немного ограничено. Но вы сказали о 100 МБ - так что это не должно быть проблемой. И еще одна вещь - расширение размера файла mmaped требует некоторой работы.

Кстати, это ТАКОЕ обсуждение также может дать вам некоторые идеи.

0 голосов
/ 21 апреля 2009

Как упомянуто выше, используйте NIO (a.k.a. new IO). Также выходит новый IO.

Правильное использование решения для жесткого диска RAID поможет вам, но это будет проблемой.

Мне очень нравится идея сжатия данных. Перейти на gzipoutputstream чувак! Это удвоит вашу пропускную способность, если процессор сможет поддерживать скорость. Вполне вероятно, что вы можете воспользоваться преимуществами стандартных двухъядерных машин, а?

-Stosh

0 голосов
/ 12 февраля 2009

Если вы напишите меньше байтов, это будет быстрее. Что если вы отфильтровали его через gzipoutputstream или что, если вы записали свои данные в ZipFiles или JarFiles?

...