Какой лучший способ записи в большее количество файлов, чем позволяет ядро ​​одновременно открывать? - PullRequest
3 голосов
/ 16 июня 2010

У меня очень большой двоичный файл, и мне нужно создать отдельные файлы на основе идентификатора во входном файле.Есть 146 выходных файлов, и я использую cstdlib и fopen и fwrite.FOPEN_MAX равно 20, поэтому я не могу держать все 146 выходных файлов открытыми одновременно.Я также хочу минимизировать количество раз, когда я открываю и закрываю выходной файл.

Как эффективно записывать в выходные файлы?

Я также должен использовать библиотеку cstdlib из-за устаревшего кода.

Исполняемый файл также должен быть совместим с UNIX и Windows.

Ответы [ 11 ]

5 голосов
/ 16 июня 2010

Несколько возможных подходов:

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

  • Обрабатывайте выходную буферизацию самостоятельно, а не позволяйте библиотеке делать это за вас: сохраняйте свой собственный набор из 146 (или столько, сколько вам может понадобиться) выходных буферов и буферизуйте выходные данные, а также выполняйте открытие / сброс / закрыть, когда определенный выходной буфер заполнен. Вы могли бы даже объединить это с вышеупомянутым подходом, чтобы действительно минимизировать операции открытия / закрытия.

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

3 голосов
/ 16 июня 2010

Может также стоить отсканировать входной файл, составив список каждого выходного идентификатора и отсортировав его так, чтобы вы сначала записали все записи file1, затем все записи file2 и т. Д.

1 голос
/ 16 июня 2010

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

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

0 голосов
/ 16 июня 2010

Стратегия «Открыт мало файлов»:

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

Псевдокод для каждого потока:

  1. Запустите файл, соберите все уникальные идентификаторы.
  2. fseek() вернуться к началу ввода.
  3. Для каждой группы из 19 идентификаторов:
    1. Открыть файл для каждого идентификатора.
    2. Пробежать по входному файлу, добавив соответствующие записи в соответствующий выходной файл.
    3. Закрыть эту группу из 19 выходных файлов.
    4. fseek() к началу ввода.

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

Стратегия "Наименьшее количество файловых операций"

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

  1. Выберите следующую непрочитанную страницу ввода.
  2. Сортировка этого ввода в двухстраничные буферы, по одному буферу для каждого выходного файла. Всякий раз, когда одна страница буфера заполнена:
    1. Пометить страницу как недоступную.
    2. Если эта страница имеет наименьшее значение счетчика страниц, добавьте его в файл, используя fwrite(). Если нет, подождите, пока оно не станет самым низким (надеюсь, этого не произойдет).
    3. Пометить страницу как доступную и присвоить ей номер следующей страницы.

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

О чем следует помнить:

  • Выровнена ли ваша страница данных? Если нет, то вам нужно уметь читать «следующую страницу».
  • Убедитесь, что у вас нет двух потоков fwrite() в одном и том же выходном файле одновременно. Если это произойдет, вы можете испортить одну из страниц.
0 голосов
/ 16 июня 2010

Предполагается, что вы работаете в системе * nix, ограничение на процесс, а не для всей системы. Это означает, что вы можете запустить несколько процессов, каждый из которых отвечает за подмножество идентификаторов, по которым вы фильтруете. Каждый мог оставить в FOPEN_MAX для своего процесса.

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

0 голосов
/ 16 июня 2010

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

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

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

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

Основным ограничением является размер ваших двоичных данных. Это больше чем кеш? больше памяти? потекли из магнитофона? Постоянно отходит от сенсорного потока и существует только как «файл» в памяти? Каждый из них представляет свою стратегию оптимизации ...

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

0 голосов
/ 16 июня 2010

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

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

Предел одновременного открытия файлов устанавливается операционной системой.Например, если ваша ОС имеет максимум 10, вы должны принять меры при запросе 11-го файла.

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

0 голосов
/ 16 июня 2010

Вы можете сделать это в 2 шага.

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

2) Для каждого созданного файла создайте 19 (только 13 для последнего) новых файлов и запишите в него идентификаторы..

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

Я думаю, что в большинстве случаев более эффективно открывать и закрывать файлы больше.часто.

0 голосов
/ 16 июня 2010

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

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

0 голосов
/ 16 июня 2010

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

...