Удалить все, кроме последних 500 000 байтов из файла с STL - PullRequest
1 голос
/ 06 декабря 2008

Наш класс ведения журнала при инициализации усекает файл журнала до 500 000 байт. С этого момента записи журнала добавляются в файл.

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

Очевидно, что сохранять первые 500 000 байтов бесполезно, поэтому мы сохраняем последние 500 000 байтов.

Наше решение имеет серьезную проблему с производительностью. Какой эффективный способ сделать это?

Ответы [ 8 ]

6 голосов
/ 06 декабря 2008

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

Я думаю, тебе лучше просто:

#include <fstream>
std::ifstream ifs("logfile");  //One call to start it all. . .
ifs.seekg(-512000, std::ios_base::end);  // One call to find it. . .
char tmpBuffer[512000];
ifs.read(tmpBuffer, 512000);  //One call to read it all. . .
ifs.close();
std::ofstream ofs("logfile", ios::trunc);
ofs.write(tmpBuffer, 512000); //And to the FS bind it.

Это позволяет избежать переименования файла, просто скопировав последние 512 КБ в буфер, открыв файл журнала в режиме усечения (очищает содержимое файла журнала), и выплеснув те же 512 К обратно в начало файла.

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

Вы можете загрузить 512K в буфер в памяти, закрыть входной поток, а затем открыть выходной поток; таким образом, вам не понадобятся два файла, так как вы будете вводить, закрывать, открывать, выводить 512 байт, а затем идти. Таким образом вы избегаете магии переименования / перемещения файлов.

Если вы не испытываете отвращения к смешиванию C с C ++ в некоторой степени, вы также можете:

(Примечание: псевдокод; я не помню, чтобы вызов mmap шел с макушки головы)

int myfd = open("mylog", O_RDONLY); // Grab a file descriptor
(char *) myptr = mmap(mylog, myfd, filesize - 512000) // mmap the last 512K
std::string mystr(myptr, 512000) // pull 512K from our mmap'd buffer and load it directly into the std::string
munmap(mylog, 512000); //Unmap the file
close(myfd); // Close the file descriptor

В зависимости от многих вещей, mmap может быть на быстрее, чем поиск. Попытка гуглить «fseek vs mmap» дает интересное прочтение, если вам интересно.

НТН

3 голосов
/ 08 декабря 2008

Если вы используете окна, не пытайтесь копировать части вокруг. Просто скажите Windows, что вам больше не нужны первые байты, позвонив FSCTL_SET_SPARSE и FSCTL_SET_ZERO_DATA

3 голосов
/ 06 декабря 2008

Я бы наверное:

  • создать новый файл.
  • искать в старом файле.
  • сделать буферизованное чтение / запись из старого файла в новый файл.
  • переименуйте новый файл поверх старого.

Чтобы выполнить первые три шага (проверка ошибок пропущена, например, я не могу вспомнить, что делает seekg, если размер файла меньше 500 КБ):

#include <fstream>

std::ifstream ifs("logfile");
ifs.seekg(-500*1000, std::ios_base::end);
std::ofstream ofs("logfile.new");
ofs << ifs.rdbuf();

Тогда я думаю, что вы должны сделать что-то нестандартное, чтобы переименовать файл.

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

Я не уверен, почему поиск идет медленно, поэтому я могу что-то упустить. Я не ожидал бы, что время поиска зависит от размера файла. Что может зависеть от файла, так это то, что я не уверен, обрабатывают ли эти функции файлы размером более 2 ГБ в 32-разрядных системах.

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

const int bufsize = 64*1024; // or whatever
std::vector<char> buf(bufsize);
...
ifs.rdbuf()->pubsetbuf(&buf[0], bufsize);

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

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

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

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

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

1 голос
/ 06 декабря 2008

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

Тогда класс журналирования просматривает старые файлы и удаляет самый старый

Регистратор будет иметь два параметра конфигурации.

  1. 500k для порога начала нового журнала
  2. количество старых журналов, которые нужно хранить.

Таким образом, управление файлами журналов будет самообслуживающимся.

1 голос
/ 06 декабря 2008

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

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

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

Иллюстрация с N = 6 («|» обозначает заполнение пробелами до тех пор):

#myapp logfile, lines = 6, width = 80, pointer = 4                              |
[2008-12-01 15:23] foo bakes a cake                                             |
[2008-12-01 16:15] foo has completed baking a cake                              |
[2008-12-01 16:16] foo eats the cake                                            |
[2008-12-01 16:17] foo tells bar: I have made you a cake, but I have eaten it   |
[2008-12-01 13:53] bar would like some cake                                     |
[2008-12-01 14:42] bar tells foo: sudo bake me a cake                           |
0 голосов
/ 06 декабря 2008

Итак, вам нужен конец файла - вы копируете его в какой-то буфер, чтобы что-то с ним делать? Что вы имеете в виду «записывает это обратно» в файл. Вы имеете в виду, что он перезаписывает файл, обрезая при инициализации до 500 Кбайт оригинала + что он добавляет?

Предложения:

  • Переосмыслите, что вы делаете. Если это работает и это то, что нужно, что с ним не так? Зачем менять? есть проблема с производительностью? Вы начинаете задумываться, куда делись все ваши записи? Для такого типа вопросов больше всего помогает решить проблему, а не опубликовать существующее поведение. Никто не может полностью прокомментировать это, если они не знают полной проблемы - потому что это субъективно.

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

0 голосов
/ 06 декабря 2008

В Widefinder 2 много говорят об эффективном доступном вводе-выводе (или, точнее, ссылки в столбце «Примечания» содержат много информации об эффективном вводе-выводе).

Отвечая на ваш вопрос:

  1. (Заголовок) Удалить первые 500 000 байтов из файла с [стандартной библиотекой]

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

Было бы эффективно просто искать в файле 500 000 байт, а затем запускать буферизованную копию в новый файл. Но как только вы это сделаете, в стандартной библиотеке нет готовой функции «переименовать этот файл». Собственные функции ОС могут эффективно переименовывать файлы, как Boost.Filesystem или STLSoft.

  1. (Актуальный вопрос) Наш класс протоколирования при инициализации ищет до 500 000 байт до конца файла, копирует остальное в std::string и затем записывает это обратно в файл.

В этом случае вы отбрасываете последний бит файла, и это очень легко сделать за пределами стандартной библиотеки. Просто используйте операции с файловой системой, чтобы установить размер файла 500 000 байт (например, ftruncate, SetEndOfFile). Все, что после этого будет проигнорировано.

0 голосов
/ 06 декабря 2008

Я не думаю, что это что-то связанное с компьютером, но как вы, ребята, написали свой класс логирования. Мне кажется странным, что вы читаете последние 500k в строку, зачем вы это делаете?

Просто добавьте файл журнала.

  fstream myfile;
  myfile.open("test.txt",ios::app);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...