【问题标题】:Accessing individual characters in a file inefficient? (C++)访问文件中的单个字符效率低下? (C++)
【发布时间】:2012-11-28 07:47:29
【问题描述】:

我一直认为在处理文本文件时,首先将内容(或其中的一部分)读入 std::string 或 char 数组会更有效,因为——根据我的有限理解——文件被读取以比单个字符大得多的块从内存中提取。但是,我听说现代操作系统通常实际上并不直接从文件中读取,这使得我手动缓冲输入几乎没有什么好处。

假设我想确定文本文件中某个字符的数量。下面会不会低效?

while (fin.get(ch)) {
    if (ch == 'n')
        ++char_count;
}

当然,我想这取决于文件大小,但有人对最佳方法有什么一般规则吗?

【问题讨论】:

  • 可能值得一读:why GNU grep is fast
  • 我还没有测量过这种方法的性能,但是如何使用带有自定义分隔符的 std::ifstream::getline (作为您想要计数的字符)?我可以想象这比一次读取一个字符更快。
  • 我应该澄清一下,我对计数字符示例的特定解决方案不是很感兴趣,而是对如何实际访问文件的解释。当我检索单个字符时,我实际上是一次从硬盘上的某个位置读取一个字符,还是它被自动缓冲在下面?
  • 它肯定在下面为你缓冲。事实上,尝试使用巧妙的技巧为操作系统缓冲可能会降低代码的效率。
  • @user - 这将取决于操作系统以及您使用的读取方法,但我敢打赌,大多数时候操作系统或库已经在为你。

标签: c++ file-io fstream


【解决方案1】:

不,您的代码是有效的。文件旨在按顺序读取。在幕后,保留一块 RAM 以缓冲传入的数据流。事实上,因为您在读取整个文件之前就开始处理数据,所以您的 while 循环应该会稍早完成。此外,您可以毫无问题地处理远远超出计算机主 RAM 的文件。

编辑:令我惊讶的是,Jerry 的号码出现了。我会假设通过读取和解析块获得的任何效率都会与从文件读取的成本相形见绌。我真的很想知道这些时间花在了哪里,以及当文件没有被缓存时变化会降低多少。尽管如此,我不得不推荐 Jerry 的答案,尤其是他指出,在您知道自己有性能问题之前,您真的不应该担心它。

【讨论】:

  • 感谢您的回复。我担心的一个问题是我应该根据文件的大小以不同的方式处理文件。鉴于您所说的,似乎我最好将所有文本文件都视为流,并让 C++ 完成工作。
  • 一般我会这么说,是的。只要您从头到尾阅读它,我就不会考虑缓存文件。在另一个极端,如果您是随机或以相反的顺序读取字节,您肯定会考虑先将所有内容加载到内存中。
【解决方案2】:

这很大程度上取决于性能对您/您的应用程序的真正重要性。反过来,这往往取决于您正在处理的文件有多大——如果您正在处理数十或数百千字节的文件,您通常应该只编写可以工作的最简单的代码,而不必太担心它——你能做的任何事情基本上都是瞬间完成的,所以优化代码不会真正完成太多。

另一方面,如果您要处理大量数据(大约数十兆字节或更多),效率上的差异可能会变得相当大。除非您采取相当具体的步骤来绕过它(例如使用read),否则您的所有读取都将被缓冲——但这并不意味着它们都将具有相同的速度(或必然甚至非常接近相同的速度)。

例如,让我们快速测试几种不同的方法,基本上可以完成您所要求的工作:

#include <stdio.h>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <fstream>
#include <time.h>
#include <string>
#include <algorithm>

unsigned count1(FILE *infile, char c) { 
    int ch;
    unsigned count = 0;

    while (EOF != (ch=getc(infile)))
        if (ch == c)
            ++count;
    return count;
}

unsigned int count2(FILE *infile, char c) { 
    static char buffer[4096];
    int size;
    unsigned int count = 0;

    while (0 < (size = fread(buffer, 1, sizeof(buffer), infile)))
        for (int i=0; i<size; i++)
            if (buffer[i] == c)
                ++count;
    return count;
}

unsigned count3(std::istream &infile, char c) {    
    return std::count(std::istreambuf_iterator<char>(infile), 
                    std::istreambuf_iterator<char>(), c);
}

unsigned count4(std::istream &infile, char c) {    
    return std::count(std::istream_iterator<char>(infile), 
                    std::istream_iterator<char>(), c);
}

template <class F, class T>
void timer(F f, T &t, std::string const &title) { 
    unsigned count;
    clock_t start = clock();
    count = f(t, 'N');
    clock_t stop = clock();
    std::cout << std::left << std::setw(30) << title << "\tCount: " << count;
    std::cout << "\tTime: " << double(stop-start)/CLOCKS_PER_SEC << "\n";
}

int main() {
    char const *name = "test input.txt";

    FILE *infile=fopen(name, "r");

    timer(count1, infile, "ignore");

    rewind(infile);
    timer(count1, infile, "using getc");

    rewind(infile);
    timer(count2, infile, "using fread");

    fclose(infile);

    std::ifstream in2(name);
    in2.sync_with_stdio(false);
    timer(count3, in2, "ignore");

    in2.clear();
    in2.seekg(0);
    timer(count3, in2, "using streambuf iterators");

    in2.clear();
    in2.seekg(0);
    timer(count4, in2, "using stream iterators");

    return 0;
}

我用一个大约 44 兆字节的文件作为输入来运行它。用VC++2012编译时,得到如下结果:

ignore                          Count: 400000   Time: 2.08
using getc                      Count: 400000   Time: 2.034
using fread                     Count: 400000   Time: 0.257
ignore                          Count: 400000   Time: 0.607
using streambuf iterators       Count: 400000   Time: 0.608
using stream iterators          Count: 400000   Time: 5.136

使用相同的输入,但使用 g++ 4.7.1 编译:

ignore                          Count: 400000   Time: 0.359
using getc                      Count: 400000   Time: 0.339
using fread                     Count: 400000   Time: 0.243
ignore                          Count: 400000   Time: 0.697
using streambuf iterators       Count: 400000   Time: 0.694
using stream iterators          Count: 400000   Time: 1.612

因此,即使所有读取都被缓冲,我们看到 g++ 的变化率约为 8:1,而 VC++ 的变化率约为 20:1。当然,我还没有测试(甚至接近)读取输入的所有可能方式。如果我们测试更多的阅读技巧,我怀疑我们会看到更广泛的时间,但我可能错了。无论我们是否这样做,我们都看到了足够多的变化,至少如果您正在处理大量数据,那么您很可能有理由选择一种技术而不是另一种技术来提高处理速度。

【讨论】:

    【解决方案3】:

    这在很大程度上取决于上下文,由于没有围绕代码的上下文,所以很难说。

    毫无疑问,您的操作系统可能正在为您缓存至少一小部分文件,正如其他人所说...但是,在用户和内核之间来回切换是昂贵的,这可能是您的瓶颈所在来自。

    如果您要在此代码之前插入fin.rdbuf()-&gt;pubsetbuf(NULL, 65536);,您可能会注意到显着的加速。这是标准库尝试一次从内核读取 65536 字节并将其保存以供以后使用的提示,而不是在用户和内核之间为每个字符来回切换。

    【讨论】:

      猜你喜欢
      • 2021-01-13
      • 1970-01-01
      • 1970-01-01
      • 2012-07-28
      • 2016-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多