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

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

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

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

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

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

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

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

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


person noahlz    schedule 11.02.2009    source источник


Ответы (7)


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

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

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

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

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

person Gary    schedule 11.02.2009
comment
Выбрал этот ответ, чтобы распределить точки вокруг. Кроме того, фраза или просто О производительность в вашем случае действительно запала мне в душу. - person noahlz; 13.03.2009

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

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

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

person Tom Hawtin - tackline    schedule 11.02.2009
comment
Приложение пишет на диск намного чаще (›99%), чем читает. Не могли бы вы уточнить, что вы подразумеваете под линейным доступом, это кажется немного бессмысленным - применимо ли оно к операциям добавления? - person noahlz; 11.02.2009
comment
Операции добавления будут линейными (файловая система может фрагментировать ваш файл, но это должно быть незначительной проблемой). - person Tom Hawtin - tackline; 11.02.2009

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

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

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

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

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

person fbonnet    schedule 11.02.2009

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

person TraderJoeChicago    schedule 02.12.2012
comment
Я обнаружил, что мне приходилось периодически вызывать force(), чтобы убедиться, что мои изменения попали в файл. Следовательно, он был медленнее. Конечно, это было несколько лет назад... - person noahlz; 03.12.2012
comment
Вам НЕ нужно вызывать force(), если вы не хотите защитить себя от сбоя ОС. - person TraderJoeChicago; 16.02.2013
comment
Ссылка в ответе недействительна. - person prayagupd; 21.05.2021

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

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

person Anonymous    schedule 11.02.2009
comment
На самом деле 100 МБ - так что до гигабайта на файл. И некоторые развертывания приложения имеют несколько таких файлов! Я отредактирую, чтобы быть яснее. - person noahlz; 11.02.2009

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

person Ron    schedule 12.02.2009
comment
Затем вы обмениваете накладные расходы на операции ввода-вывода на накладные расходы на кодирование/декодирование данных. Потребуются некоторые эксперименты, чтобы увидеть, жизнеспособно ли это. - person noahlz; 12.02.2009

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

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

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

-Стош

person johnstosh    schedule 21.04.2009