访问文件中的单个字符效率低下? (C ++)

时间:2012-12-10 20:30:51

标签: c++ file-io fstream

在处理文本文件时,我总是认为它更有效率,首先将内容(或部分内容)读入std :: string或char数组,因为 - 从我有限的理解 - 读取文件来自内存的块远远大于单个字符的大小。但是,我听说现代操作系统通常不会直接从文件中读取,这使得我手动缓冲输入效果不大。

说我想确定文本文件中某个字符的编号。以下是否效率低下?

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

当然,我想这取决于文件大小,但有没有人对什么是最好的方法有任何一般规则?

3 个答案:

答案 0 :(得分:12)

这里很大程度上取决于您/您的应用程序的确切关键性能。反过来,这往往取决于你正在处理的文件有多大 - 如果你正在处理几十或几百千字节的事情,你通常应该编写最简单的代码,而不用担心它 - 你可以做的任何事情都是基本上是即时的,所以优化代码并不会真正实现太多。

另一方面,如果您正在处理大量数据 - 大约数十兆字节或更多,那么效率差异就会变得相当大。除非你采取相当具体的步骤来绕过它(例如使用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。当然,我没有测试(甚至接近)读取输入的每种可能方式。如果我们测试更多的阅读技术,我怀疑我们会看到更广泛的时间,但我可能错了。无论我们是否这样做,我们都会看到足够的变化,至少如果你处理大量数据,你可以选择一种技术而不是另一种技术来提高处理速度。

答案 1 :(得分:0)

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

编辑:令我惊讶的是,杰里的号码已经消失了。我原以为通过读取和解析块而获得的任何效率都会因读取文件的成本而相形见绌。我真的很想知道这个时间花在哪里以及文件未缓存时变化的程度。不过,我必须推荐Jerry对这个问题的回答,特别是他指出,在你知道自己遇到性能问题之前,你真的不应该担心它。

答案 2 :(得分:0)

这在很大程度上取决于上下文,由于缺少代码的上下文,因此很难说。

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

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