Перекрывающийся ввод-вывод или сопоставление файлов?

В приложении Windows у меня есть класс, который оборачивает имя файла и буфер. Вы создаете его с именем файла, и вы можете запросить объект, чтобы увидеть, заполнен ли буфер, возвращая nullptr, если нет, и адрес буфера, если это так. Когда объект выходит из области видимости, буфер освобождается:

class file_buffer
{
public:
    file_buffer(const std::string& file_name);
    ~file_buffer();
    void* buffer();

private:
    ...
}

Я хочу поместить данные в память асинхронно, и, насколько я понимаю, у меня есть два варианта: либо создать буфер и использовать перекрывающийся ввод-вывод через ReadFileEx, либо использовать MapViewOfFile и коснуться адреса в другом потоке.

В настоящее время я использую ReadFileEx, который создает некоторые проблемы, поскольку запросы размером более 16 МБ склонны к сбою: я могу попытаться разделить запрос, но тогда у меня возникнут проблемы с синхронизацией, и если объект выйдет за пределы до того, как IO будет завершено У меня проблемы с очисткой буфера. Кроме того, если несколько экземпляров класса создаются в быстрой последовательности, все становится очень запутанным.

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

Это приложение должно поддерживать одноядерные машины, поэтому мой вопрос: будут ли ошибки страницы в другом программном потоке дороже, чем перекрывающийся ввод-вывод в текущем потоке? Не затормозят ли они процесс? Задерживает ли перекрывающийся ввод-вывод процесс таким же образом или есть какая-то магия ОС, которую я не понимаю? Выполняются ли отказы страниц с использованием перекрывающихся операций ввода-вывода?

Я хорошо изучил следующие темы: http://msdn.microsoft.com/en-us/library/aa365199(v=vs.85).aspx (концепции ввода-вывода в управлении файлами) http://msdn.microsoft.com/en-us/library/windows/desktop/aa366556(v=vs.85).aspx (сопоставление файлов), но я не могу понять, как добиться компромисса в производительности.


person hatcat    schedule 23.02.2013    source источник
comment
Ву ху! У меня есть значок перекати-поля. Может быть, мне следует добавить тег C++...   -  person hatcat    schedule 04.03.2013


Ответы (2)


Вы определенно захотите использовать файлы с отображением памяти. Перекрывающийся ввод-вывод (с FILE_FLAG_NO_BUFFERING) годами пропагандировался некоторыми людьми как «самый быстрый способ получить данные в ОЗУ», но это верно только в очень надуманных случаях с очень специфическими условиями. В обычном, среднестатистическом случае отключение буферного кеша — серьезная антиоптимизация.

Теперь перекрывающийся ввод-вывод без FILE_FLAG_NO_BUFFERINGимеет все особенности перекрывающегося ввода-вывода и примерно на 50 % медленнее (по причине, которую я до сих пор не могу понять).

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

Перекрывающийся ввод-вывод использует больше ЦП, намного медленнее при использовании буферного кеша, асинхронный возвращается к синхронному при некоторых хорошо документированных и некоторых недокументированных условиях (например, шифрование, сжатие и... чистая случайность? размер запроса? количество запросов?), зависание вашего приложения в непредсказуемое время.
Отправка запросов иногда может занимать «забавное» количество времени, и CancelIO иногда ничего не отменяет, а ждет завершения. Процессы с невыполненными запросами невозможно убить. Управление буферами с незавершенными перекрывающимися записями — нетривиальная дополнительная работа.

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

Я даже использую сопоставление файлов для записи сейчас, когда мне нужно записать больше нескольких байтов. Это несколько нетривиально (приходится вручную увеличивать/предварительно выделять файлы и сопоставления и усекать до фактической длины при закрытии), но с некоторыми вспомогательными классами это вполне выполнимо. Напишите 500 МБ данных, и это займет «нулевое время» (в основном вы делаете memcpy, фактическая запись происходит в фоновом режиме, в любое время позже, даже после завершения вашей программы). Удивительно, насколько хорошо это работает, даже если вы знаете, что это естественно для операционной системы.
Конечно, лучше не допускать отключения питания до того, как ОС запишет все страницы, но это верно для любой вид письма. Чего еще нет на диске, его нет на диске — на самом деле больше сказать об этом нечего. Если вы должны быть уверены в этом, вам нужно дождаться завершения синхронизации диска, и даже тогда вы не можете быть уверены, что свет не погаснет, пока вы ждете синхронизации. Такова жизнь.

person Damon    schedule 04.03.2013

Я не утверждаю, что понимаю это лучше, чем вы, поскольку, похоже, вы провели некоторое исследование. И чтобы быть полностью уверенным, вам нужно будет поэкспериментировать. Но это мое понимание проблем в обратном порядке:

  1. Сопоставление файлов и перекрывающийся ввод-вывод в Windows — это разные имплементации, и ни одна из них не зависит от другой внутри. Но оба используют уровень асинхронных блочных устройств. Насколько я понимаю, в ядре каждый ввод-вывод на самом деле асинхронен, но некоторые пользовательские операции ждут его завершения и поэтому создают иллюзию синхронности.
  2. Начиная с пункта 1, если поток выполняет ввод-вывод, другие потоки того же процесса не останавливаются. Это, если только системные ресурсы недостаточны или эти другие потоки сами выполняют ввод-вывод и сталкиваются с какой-то конкуренцией. Это будет верно независимо от того, какой тип ввода-вывода выполняет первый поток: блокирующий, неблокирующий, перекрывающийся, отображаемый в память.
  3. В файлах с отображением в память данные считываются по крайней мере по одной странице за раз, возможно, больше из-за упреждающего чтения, но вы не можете быть в этом уверены. Таким образом, зондирующий поток должен будет касаться отображаемой памяти хотя бы один раз на каждой странице. Это будет что-то вроде probe/block-probe-probe-probe-probe/block-probe... Это может быть немного менее эффективно, чем большое перекрывающееся чтение нескольких МБ. Или, может быть, программисты ядра были умнее, и это даже более эффективно. Вам придется сделать небольшое профилирование... Эй, вы можете даже обойтись без зондирующей нити и посмотреть, что произойдет.
  4. Cancelling overlapping operations is a PITA, so my recommendation will be to go with the memory-mapped files. That is way easier to set up and you get extra functionality:
    1. the memory is usable even before it is fully in memory
    2. память может/будет совместно использоваться несколькими экземплярами процесса
    3. если память находится в кеше, она будет готова мгновенно, а не просто быстро.
    4. если данные только для чтения, можно защитить память от записи, отловив баги.
person rodrigo    schedule 04.03.2013
comment
Спасибо за ваш вклад; это соответствует моему пониманию ситуации. Я бы предпочел подход с отображением памяти, но я отлаживал, тестировал код, и продажа замены требует повышения производительности. - person hatcat; 04.03.2013
comment
@hatcat: вы также можете попытаться использовать FILE_FLAG_SEQUENTIAL_SCAN при открытии файла, если вы еще не используете его. Это может повысить производительность как перекрывающихся, так и ммапированных операций ввода-вывода. - person rodrigo; 04.03.2013