С++: как оптимизировать IO? - программирование
Подтвердить что ты не робот

С++: как оптимизировать IO?

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

Более конкретно: Я могу предварительно вычислить огромное количество информации - тонны вероятностей (long double), тонну std::map<int,int> и многое другое - и сохранить все это на диск (несколько Gb).

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

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

Существуют ли какие-либо стратегии для ускорения ввода-вывода?

У меня уже есть программа, параллельная (MPI, через boost::mpi) по другим причинам, но независимо от того, что доступ к файлам на диске делает мое время вычисления невыносимым.

Любые стратегии или оптимизации?

В настоящее время я делаю все с cstdio, т.е. no iostream. Будет ли это иметь большое значение?

4b9b3361

Ответ 1

Вещи, которые не находятся на карте, просты. Вы помещаете все в один непрерывный кусок памяти, который вы знаете (например, большой массив или структура/класс без указателей), а затем используйте write(), чтобы записать его. Позже используйте read(), чтобы прочитать его, за одну операцию. Если размер может отличаться, используйте одну операцию, чтобы прочитать один int с размером, выделить память, а затем использовать один read(), чтобы втянуть его.

Часть карты немного сложнее, так как вы не можете сделать все за одну операцию. Здесь вам нужно придумать соглашение о его сериализации. Чтобы сделать i/o как можно быстрее, лучше всего конвертировать его с карты в форму в памяти, которая находится в одном месте, и вы можете быстро и быстро конвертировать обратно на карту. Если, например, ваши ключи являются int и ваши значения имеют постоянный размер, вы можете создать массив ключей и массив значений, скопировать ключи в один массив и значения в другой, а затем write() два массива, возможно, выписывая их размер. Опять же, вы читаете вещи всего двумя или тремя вызовами read().

Обратите внимание, что ничто никогда не переводилось в ASCII, и существует минимальное количество системных вызовов. Файл не будет читабельным для человека, но он будет компактным и быстрым для чтения. Три вещи делают медленное в/в: 1) системные вызовы, если вы используете небольшие чтения/записи; 2) перевод в/из ASCII (printf, scanf); 3) скорость диска. Трудно сделать около 3) (кроме SSD). Вы можете сделать чтение в фоновом потоке, но вам может потребоваться блокировать ожидание данных.

Ответ 2

Конечно, самым быстрым (но хрупким) решением было бы mmap данных на фиксированный адрес. Поместите все это в один большой struct и создайте экземпляр std:::map с помощью распределителя, который будет выделяться в блоке, прикрепленном к концу структуры. Это не просто, но будет быстро; один вызов mmap, и данные находятся в вашей (виртуальной) памяти. И поскольку вы вынуждаете адрес в mmap, вы даже можете хранить указатели и т.д.

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

Ответ 3

Некоторые рекомендации:

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

В идеале я бы постарался поместить все длинные удвоения в файл с отображением памяти и все карты в двоичные файлы.

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

Ответ 4

Эти рекомендации по загрузке всех данных в ОЗУ хороши, когда выполняются два условия:

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

(обычно они выполняются, когда какое-то приложение работает в течение длительного времени, обрабатывая разные данные)

Однако для других случаев могут быть рассмотрены другие варианты. Например. важно понять, действительно ли шаблон доступа случайный. Если нет, просмотрите данные переупорядочения, чтобы гарантировать, что элементы, которые доступны вместе, близки друг к другу. Это гарантирует, что кеширование ОС будет работать в лучшем виде, а также уменьшит время поиска жесткого диска (не обязательно для SSD, конечно).

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

Для Windows это может быть системный сервис, для других операционных систем доступны другие параметры.

Ответ 5

Кэш, кеш, кеш. Если это всего несколько ГБ, должно быть возможно кэшировать большинство, если не все ваши данные, в чем-то вроде memcached. Это особенно хорошее решение, если вы используете MPI на нескольких компьютерах, а не только на нескольких процессорах на одном компьютере.

Если все работает на одном компьютере, рассмотрите кеш разделяемой памяти, если у вас есть доступная память.

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

Ответ 6

Как было сказано, кешируйте столько, сколько вы можете в памяти.

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

Одним из распространенных методов является "Least Recent Used Algorithm" для определения того, какая страница будет заменена.

Ответ 7

Это действительно зависит от того, сколько памяти доступно и каков шаблон доступа.


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

Вам нужно изучить свой шаблон доступа, чтобы узнать, можете ли вы объединить значения, которые часто используются вместе. Это поможет ОС лучше кэшировать эти значения (т.е. Хранить их в памяти для вас, а не всегда идти на диск, чтобы читать их).


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

Затем вы можете получить доступ только к требуемому набору файлов.


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

Идея заключается в использовании сериализации и сжатия. Вы пишете несколько файлов, среди которых индекс, и сжимаете их все (zip). Затем во время запуска вы начинаете с загрузки индекса и сохранения его в памяти.

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

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

Ответ 8

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

Чтение и запись в партиях вместо записи по записи поможет, если вы используете доступ к диску другим процессом.

Ответ 9

В частности: я могу предварительно вычислить огромное количество информации - тонны вероятностей (длинный двойной), тонну std:: map и многое другое - и сохранить все это на диск (несколько Gb).

Насколько я понял, std::map также предварительно вычислены, и нет операций вставки/удаления. Только поиск. Как насчет идеи заменить карты на что-то вроде std:: hash_map или sparsehash. Теоретически это может дать выигрыш в производительности.

Ответ 10

В частности: я могу предварительно вычислить огромное количество информации - тонны вероятностей (длинный двойной), тонну std:: map и многое другое - и сохранить все это на диск (несколько Gb).

Не изобретайте велосипед. Я бы предложил использовать хранилище данных с ключом, например berkeley db: http://docs.oracle.com/cd/E17076_02/html/gsg/C/concepts.html

Это позволит сохранять и распространять файлы, кэшируя части, которые вы на самом деле используете, и сохраняете другие части на диске.