13

我现在想处理硬盘上文件的每一行。将文件作为一个整体加载然后根据换行符拆分(使用 boost)更好,还是使用更好getline()?我的问题是getline()在调用时读取单行(导致多个硬盘访问)还是读取整个文件并逐行给出?

4

6 回答 6

6

getlineread()在 C 库的深处作为系统调用调用。具体调用多少次,调用方式取决于C库设计。但很可能一次读取一行与整个文件没有明显区别,因为底层的操作系统一次将读取(至少)一个磁盘块,并且很可能至少读取一个“页面“(4KB),如果不是更多的话。

此外,除非您在阅读字符串后几乎什么都不做(例如,您正在编写诸如“grep”之类的内容,所以主要只是阅读以查找字符串),一次读取一行的开销不太可能是您花费的大部分时间。

但是“一次性加载整个文件”有几个不同的问题:

  1. 在您阅读整个文件之前,您不会开始处理。
  2. 您需要足够的内存来将整个文件读入内存 - 如果文件大小为几百 GB,该怎么办?你的程序会失败吗?

不要尝试优化某些东西,除非您使用分析来证明它是您的代码运行缓慢的部分原因。你只会给自己制造更多的问题。

编辑:所以,我写了一个程序来衡量这个,因为我认为这很有趣。

结果肯定很有趣——为了公平起见,我创建了三个每个 1297984192 字节的大文件(通过将所有源文件复制到一个包含十几个不同源文件的目录中,然后将该文件复制几次以“相乘”它,直到运行测试花费超过 1.5 秒,这是我认为您需要运行多长时间以确保时间不会太容易受到随机“网络数据包进入”或其他一些外部影响而导致超时过程)。

我还决定按流程衡量系统和用户时间。

$ ./bigfile
Lines=24812608
Wallclock time for mmap is 1.98 (user:1.83 system: 0.14)
Lines=24812608
Wallclock time for getline is 2.07 (user:1.68 system: 0.389)
Lines=24812608
Wallclock time for readwhole is 2.52 (user:1.79 system: 0.723)
$ ./bigfile
Lines=24812608
Wallclock time for mmap is 1.96 (user:1.83 system: 0.12)
Lines=24812608
Wallclock time for getline is 2.07 (user:1.67 system: 0.392)
Lines=24812608
Wallclock time for readwhole is 2.48 (user:1.76 system: 0.707)

这是读取文件的三个不同函数(当然,还有一些代码可以测量时间和其他内容,但是为了减小这篇文章的大小,我选择不发布所有这些内容 - 我玩弄了排序看看是否这有什么不同,所以上面的结果与这里的函数顺序不同)

void func_readwhole(const char *name)
{
    string fullname = string("bigfile_") + name;
    ifstream f(fullname.c_str());

    if (!f) 
    {
        cerr << "could not open file for " << fullname << endl;
        exit(1);
    }

    f.seekg(0, ios::end);
    streampos size = f.tellg();

    f.seekg(0, ios::beg);

    char* buffer = new char[size];
    f.read(buffer, size);
    if (f.gcount() != size)
    {
        cerr << "Read failed ...\n";
        exit(1);
    }

    stringstream ss;
    ss.rdbuf()->pubsetbuf(buffer, size);

    int lines = 0;
    string str;
    while(getline(ss, str))
    {
        lines++;
    }

    f.close();


    cout << "Lines=" << lines << endl;

    delete [] buffer;
}

void func_getline(const char *name)
{
    string fullname = string("bigfile_") + name;
    ifstream f(fullname.c_str());

    if (!f) 
    {
        cerr << "could not open file for " << fullname << endl;
        exit(1);
    }

    string str;
    int lines = 0;

    while(getline(f, str))
    {
        lines++;
    }

    cout << "Lines=" << lines << endl;

    f.close();
}

void func_mmap(const char *name)
{
    char *buffer;

    string fullname = string("bigfile_") + name;
    int f = open(fullname.c_str(), O_RDONLY);

    off_t size = lseek(f, 0, SEEK_END);

    lseek(f, 0, SEEK_SET);

    buffer = (char *)mmap(NULL, size, PROT_READ, MAP_PRIVATE, f, 0);


    stringstream ss;
    ss.rdbuf()->pubsetbuf(buffer, size);

    int lines = 0;
    string str;
    while(getline(ss, str))
    {
        lines++;
    }

    munmap(buffer, size);
    cout << "Lines=" << lines << endl;
}
于 2013-01-22T16:33:14.400 回答
3

操作系统将读取整个数据块(取决于磁盘的格式化方式,通常一次 4-8k)并为您做一些缓冲。让操作系统为您处理它,并以对您的程序有意义的方式读取数据。

于 2013-01-22T16:26:04.587 回答
2

fstreams 被合理地缓冲。操作系统对硬盘的底层访问被合理缓冲。硬盘本身有合理的缓冲。如果您逐行读取文件,您肯定不会触发更多的硬盘访问。或者一个字符一个字符,就此而言。

所以没有理由将整个文件加载到一个大缓冲区并在该缓冲区上工作,因为它已经一个缓冲区中。而且通常也没有理由一次缓冲一行。为什么要分配内存来缓冲已经在 ifstream 中缓冲的字符串中的内容?如果可以的话,直接在流上工作,不要费心将所有东西从一个缓冲区扔到下一个缓冲区两次或更多次。除非它支持可读性和/或您的分析器告诉您磁盘访问会显着减慢您的程序。

于 2013-01-22T16:39:00.940 回答
1

如果可以在内存中容纳所有数据,则最好获取所有数据,因为每当您请求 I/O 时,您的程序都会失去处理并进入等待 Q。

在此处输入图像描述

但是,如果文件大小很大,那么最好一次读取处理中所需的尽可能多的数据。因为较大的读取操作将花费大量时间来完成,然后是较小的。cpu 进程切换时间比整个文件读取时间要小得多。

于 2013-01-22T16:32:07.817 回答
1

我相信 C++ 习惯用法是逐行读取文件,并在读取文件时创建基于行的容器。很可能 iostreams ( getline) 将被充分缓冲,以至于您不会注意到显着差异。

但是,对于非常大的文件,您可以通过读取较大的文件块(而不是一次读取整个文件)并在找到换行符时拆分内部来获得更好的性能。

如果您想具体了解哪种方法更快以及速度快多少,则必须分析您的代码。

于 2013-01-22T16:29:36.673 回答
0

如果它是磁盘上的一个小文件,那么读取整个文件并逐行解析它可能比一次读取一行更有效——这需要大量的磁盘访问。

于 2013-01-22T16:27:15.590 回答