Производительность / стабильность файла с отображением в памяти - Native или MappedByteBuffer - по сравнениюобычный поток вывода файла

StackOverflow https://stackoverflow.com/questions/537295

Вопрос

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-Стош

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top