fseek / перемотка в цикле - PullRequest
       22

fseek / перемотка в цикле

3 голосов
/ 03 марта 2009

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

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

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

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

Снижает ли использование fseek первую позицию / перемотку в цикле производительность?

Количество записей может быть много, иногда (до 500K).

Спасибо.

РЕДАКТИРОВАТЬ: утечка памяти уже исправлена. Решение перезапуска было предложено как дополнительная мера безопасности и средства для обеспечения механизма перезапуска вместе с решением SKIP n records. Извините, что не упомянул об этом раньше.

Ответы [ 5 ]

6 голосов
/ 03 марта 2009

Столкнувшись с такой проблемой, вы можете выбрать один из двух методов:

  1. метод, который вы предложили : для каждой прочитанной записи записать номер записи (или позицию, возвращаемую ftell во входном файле) в отдельный закладка файл. Чтобы обеспечить возобновление работы именно с того места, где вы остановились, чтобы не вводить повторяющиеся записи, вы должны fflush после каждой записи (как в bookmark, так и в файлы вывода / отклонения). Эта и другие операции записи без буферизации в целом замедляются. типичный (безотказный) сценарий значительно. Для полноты заметим, что у вас есть три способа записи в файл закладок:
    • fopen(..., 'w') / fwrite / fclose - очень медленно
    • rewind / truncate / fwrite / fflush - незначительно быстрее
    • rewind / fwrite / fflush - несколько быстрее ; Вы можете пропустить truncate, поскольку номер записи (или позиция ftell) всегда будет длиннее или длиннее, чем предыдущий номер записи (или позиция ftell), и будет полностью перезаписывать его, при условии, что вы усекаете файл один раз в запуск (ответ на ваш оригинальный вопрос)
  2. предположим, что все будет хорошо в большинстве случаев ; при возобновлении после сбоя просто посчитайте количество записей, уже выведенных (обычный вывод плюс отклонения), и пропустите эквивалентное количество записей из входного файла.
    • Это позволяет поддерживать типичные сценарии (без сбоев) очень быстро, без существенного снижения производительности в случае сценариев возобновления после сбоя.
    • Вам не нужно fflush файлы, или, по крайней мере, не так часто. Вам по-прежнему необходимо fflush основной выходной файл перед переключением на запись в файл отклонения и fflush отклоняющий файл перед переключением обратно на запись в основной выходной файл (вероятно, несколько сотен или тысяч раз для записи 500k) вход.) Просто удалите последнюю незавершенную строку из файлов вывода / отклонения, все до этой строки будет согласованным.

Я настоятельно рекомендую метод № 2 . Запись, связанная с методом # 1 (какой бы из трех возможностей) ни стоила, чрезвычайно дорогая по сравнению с любыми дополнительными (буферизованными) операциями чтения, необходимыми для метода # 2 (fflush может занять несколько миллисекунд; умножьте это на 500 Кб, и вы получите минуты - при подсчете количество строк в файле записи 500k занимает всего несколько секунд, и, кроме того, кеш файловой системы работает с , а не с вами.)


EDIT Просто хотел уточнить точные шаги, которые нужно реализовать для метода 2:

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

    • предположим, что вы записали 1000 записей в основной выходной файл, а затем
    • Вы должны записать 1 строку в файл отклонения, не вручную сначала сбрасывая основной выходной файл, затем
    • вы записываете еще 200 строк в основной выходной файл, не сбрасывая вручную файл отклонения, а затем
    • среда выполнения автоматически очищает основной выходной файл для вас, потому что вы накопили большой объем данных в буферах для основного выходного файла, то есть 1200 записей
      • но среда выполнения еще не автоматически сбросила отклоненный файл на диск для вас, поскольку файловый буфер содержит только одну запись, что недостаточно для автоматической очистки
    • ваша программа вылетает в этот момент
    • вы возобновляете и подсчитываете 1200 записей в основном выходном файле (среда выполнения удалила их для вас), но 0 (!) Записей в файле отклонений (не очищены).
    • вы возобновляете обработку входного файла с записи # 1201, при условии, что в основной выходной файл было успешно обработано только 1200 записей; отклоненная запись будет потеряна, и 1200-я действительная запись будет повторена
    • Вы не хотите этого!
  • теперь рассмотрим ручную очистку после переключения файлов вывода / отклонения:
    • предположим, что вы записываете 1000 записей в основной выходной файл, затем
    • вы обнаружите одну недействительную запись, которая принадлежит файлу отклонения; последняя запись была действительной; это означает, что вы переключаетесь на запись в файл отклонений: очистите основной выходной файл перед записью в файл отклонений
    • теперь вы записываете 1 строку в файл отклонения, затем
    • вы встретите одну действительную запись, которая принадлежит основному выходному файлу; последняя запись была недействительной; это означает, что вы переключаетесь на запись в основной выходной файл: очистите файл отклонения перед записью в основной выходной файл
    • вы записываете еще 200 строк в основной выходной файл, не сбрасывая вручную файл отклонения, затем
    • Предположим, что среда выполнения ничего не сбрасывала для вас автоматически, поскольку 200 записей, помещенных в буфер с момента последнего ручного сброса основного выходного файла, недостаточно для запуска автоматического сброса
    • ваша программа падает в этот момент
    • вы возобновляете и считаете 1000 действительных записей в основном выходном файле (вы вручную сбросили их перед переключением в файл отклонений) и 1 запись в файле отклонений (вы вручную сбросили перед переключением обратно в основной выходной файл).
    • вы правильно возобновили обработку входного файла с записи # 1001, которая является первой действительной записью сразу после недействительной записи.
    • вы обрабатываете следующие 200 действительных записей, потому что они не были сброшены, но вы не получаете ни пропущенных записей, ни дубликатов
  • если вы недовольны интервалом между автоматическими сбросами во время выполнения, вы также можете выполнять ручные сбрасывания каждые 100 или каждые 1000 записей. Это зависит от того, является ли обработка записи более дорогой, чем очистка, или нет (если обработка более дорогая, часто выполняется очистка, возможно, после каждой записи, в противном случае сброс выполняется только при переключении между выводом / отклонением).

  • восстановление после сбоя

    • откройте выходной файл и файл отклонений для чтения и записи и начните с чтения и подсчета каждой записи (скажем, в records_resume_counter), пока не достигнете конца файла
    • , если вы не очищали после каждой записи, которую вы выводите , вам также потребуется выполнить специальную обработку для последней записи как в файле вывода, так и в файле отклонений:
      • перед чтением записи из прерванного файла вывода / отклонения, запомните положение, в котором вы находитесь в указанном файле вывода / отклонения (используйте ftell), назовем его last_valid_record_ends_here
      • читать запись. проверить, что запись не является частичной записью (т.е. среда выполнения не сбросила файл до middle записи).
      • если у вас есть одна запись в строке, это легко проверить, проверив, что последний символ в записи - это возврат каретки или перевод строки (\n или `r`)
        • если запись завершена, увеличить счетчик записей и перейти к следующей записи (или концу файла, в зависимости от того, что наступит раньше).
        • если запись частичная, fseek вернуться к last_valid_record_ends_here и прекратить чтение из этих файлов вывода / отклонения; не увеличивать счетчик; перейти к следующему выводу или отклонить файл, если вы не прошли через все из них
    • открыть входной файл для чтения и пропустить records_resume_counter записей из него
      • продолжить обработку и вывод в файл output / rejects; он автоматически добавится к файлу вывода / отклонения, в котором вы прекратили чтение / подсчет уже обработанных записей
      • если вам нужно было выполнить специальную обработку для частичных сбросов записей, следующая выводимая вами запись перезапишет свою частичную информацию из предыдущего запуска (на last_valid_record_ends_here) - у вас не будет дубликатов, мусора или отсутствующих записей.
2 голосов
/ 03 марта 2009

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

Конечно, лучше всего исправить утечку памяти;)

2 голосов
/ 03 марта 2009

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

Мне кажется, что это лучшее решение, чтобы устранить основную причину проблемы, а не лечить симптомы.

fseek() и fwrite() ухудшат производительность, но далеко не так сильно, как операция открытия / записи / закрытия.

Я предполагаю, что вы будете хранить значение ftell() во втором файле (так что вы можете продолжить с того места, где остановились). Вы также должны всегда fflush() файл, чтобы обеспечить запись данных из библиотеки времени выполнения C в буферы ОС. В противном случае ваш SEGV будет гарантировать, что значение не обновлено.

0 голосов
/ 03 марта 2009

Я бы перестал копать более глубокую яму и просто запустить программу через Valgrind . Это должно устранить утечку, а также другие проблемы.

0 голосов
/ 03 марта 2009

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

...