使用STL从文件中删除除最后500,000个字节之外的所有字节

时间:2008-12-06 00:19:50

标签: c++ logging stl

我们的日志记录类在初始化时会将日志文件截断为500,000字节。从那时起,日志语句将附加到文件中。

我们这样做是为了保持低磁盘使用率,我们是商品最终用户产品。

显然保留前500,000个字节是没用的,所以我们保留最后500,000个字节。

我们的解决方案存在严重的性能问题。有效的方法是什么?

8 个答案:

答案 0 :(得分:6)

  

“我可能会创建一个新文件,在旧文件中搜索,从旧文件到新文件进行缓冲读/写,将旧文件重命名为旧文件。”

我认为你会变得更好:

#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.

这可以通过简单地将最后的512K复制到缓冲区,以截断模式打开日志文件(清除日志文件的内容),然后将相同的512K反射到文件的开头来避免文件重命名。

请注意,上面的代码尚未经过测试,但我认为这个想法应该是合理的。

您可以将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'会产生一些有趣的读物。

HTH

答案 1 :(得分:3)

我可能会:

  • 创建一个新文件。
  • 寻找旧文件。
  • 从旧文件到新文件进行缓冲读/写。
  • 将旧文件重命名为旧文件。

要执行前三个步骤(省略错误检查,例如,如果文件小于500k,我不记得seekg会做什么):

#include <fstream>

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

然后我认为你必须做一些非标准的事情来重命名文件。

显然,你需要500k的磁盘空间来实现这一点,所以如果你截断日志文件的原因是因为它刚刚填满磁盘,那就不好了。

我不确定为什么搜寻很慢,所以我可能会遗漏一些东西。我不希望寻找时间取决于文件的大小。可能取决于文件的是,我不确定这些函数是否在32位系统上处理2GB +文件。

如果副本本身很慢,那么根据平台你可以通过使用更大的缓冲区来加速它,因为这会减少系统调用的数量,也许更重要的是磁盘头必须寻找的次数在读取点和写入点之间。要做到这一点:

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

使用不同的值测试它并查看。您也可以尝试增加ofstream的缓冲区,我不确定这是否会产生影响。

请注意,在“实时”日志文件中使用我的方法很麻烦。例如,如果在副本和重命名之间附加了一个日志条目,那么您将永远丢失它,并且您尝试替换的文件上的任何打开句柄都可能导致问题(它将在Windows上失败,在Linux上失败)将替换该文件,但旧文件仍将占用空间并仍然写入,直到句柄关闭)。

如果截断是从执行所有日志记录的同一个线程完成的,那么没有问题,您可以保持简单。否则你需要使用锁或其他方法。

这是否完全可靠取决于平台和文件系统:move-and-replace可能是也可能不是原子操作,但通常不是,所以你可能不得不重命名旧文件,然后重命名新文件,然后删除旧文件,并有一个错误恢复,在启动时检测是否有重命名的旧文件,如果是,则将其放回并重新启动截断。 STL无法帮助您处理平台差异,但有boost :: filesystem。

对不起,这里有很多警告,但很多都取决于平台。如果你在个人电脑上,那么我很困惑为什么要复制一半微不足道的数据需要时间。

答案 2 :(得分:3)

如果您碰巧使用窗户,请不要打扰复制零件。只需通过调用FSCTL_SET_SPARSEFSCTL_SET_ZERO_DATA

告诉Windows,您不再需要第一个字节

答案 3 :(得分:1)

如果您可以在重新初始化之间生成几GB的日志文件,那么似乎只在初始化时截断文件并不会真正有用。

我认为我会尝试提出一种专门的文本文件格式,以便始终使用指向“当前”线环绕的指针替换内容。您需要一个恒定的线宽来分配磁盘空间一次,并将指针放在该文件的第一行或最后一行。

这样,文件永远不会增长或缩小,并且您将始终拥有最后的 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                           |

答案 4 :(得分:1)

另一种解决方案是让日志文件大小超过500k时检测日志记录类,然后打开一个新的日志文件,然后关闭旧的日志文件。

然后日志记录类将查看旧文件,并删除最旧的文件

记录器有两个配置参数。

  1. 500k表示何时开始新日志
  2. 要保留的旧日志数量。
  3. 这样,日志文件管理将是一个自我维护的东西。

答案 5 :(得分:0)

我不认为它与计算机有任何关系,但你们是如何编写日志类的。我觉得你把最后500k读成一个字符串听起来很奇怪,你为什么要那样做?

只需附加到日志文件。

  fstream myfile;
  myfile.open("test.txt",ios::app);

答案 6 :(得分:0)

Widefinder 2 has a lot of talk about efficient IO available(或者更准确地说,“Notes”列下的链接有很多关于有效IO的信息)。

回答你的问题:

  1. (标题)使用[标准库]
  2. 从文件中删除前500,000个字节

    标准库在文件系统操作方面有些限制。如果您不仅限于标准库,那么您可以非常容易地过早地结束文件(也就是说,“此后的所有内容都不再是此文件的一部分”),但是很难启动文件(“所有内容”在此之前不再是此文件的一部分“)。

    简单地在文件中寻找500,000个字节,然后将缓冲副本启动到新文件是有效的。但是一旦你这样做了,标准的库就没有现成的“重命名这个文件”功能。本机OS功能可以有效地重命名文件,Boost.Filesystem或STLSoft也是如此。

    1. (实际问题)我们的日志记录类在初始化时,在文件结束前寻找500,000个字节,将其余部分复制到std::string,然后将其写回文件。
    2. 在这种情况下,您将删除文件的最后一位,并且在标准库外部进行操作非常容易。只需使用文件系统操作将文件大小设置为500,000字节(例如,ftruncateSetEndOfFile)。之后的任何事情都将被忽略。

答案 7 :(得分:0)

所以你想要文件的结尾 - 你正在将它复制到某种缓冲区以便用它做什么?你是什​​么意思'写回来'到文件。你的意思是它覆盖文件,在init上截断原始的500k字节+它添加的内容?

建议:

  • 重新思考你在做什么。如果这有效并且是理想的,那么它有什么问题?为何改变?有性能问题吗?您是否开始怀疑所有日志条目的去向?与发布现有行为相比,这类问题最有助于提供更多问题。除非他们知道完整的问题,否则没有人可以对此作出充分的评论 - 因为它是主观的。

  • 如果是我和我的任务是重新设计您的日志记录机制,我会构建一个机制来切断日志文件:时间长度或大小。

相关问题