Вопрос

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

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

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

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

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

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

Спасибо.

Редактировать:Утечка памяти уже устранена.Решение перезапуска было предложено в качестве дополнительной меры безопасности и средства обеспечения механизма перезапуска наряду с решением для ПРОПУСКА n записей.Извините, что не упомянул об этом раньше.

Это было полезно?

Решение

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

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

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


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

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

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

  • возобновление после сбоя

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

Другие советы

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

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

fseek() и fwrite() это приведет к снижению производительности, но далеко не так сильно, как операция типа open / write / close.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top