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

Accessing individual characters in a file inefficient? (C++)

本文关键字:C++ 效率 文件 单个 字符 访问      更新时间:2023-10-16

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

假设我想确定文本文件中某个字符的编号。以下会不会效率低下?

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

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

这里很大程度上取决于性能对您/您的应用程序的关键程度。反过来,这往往取决于你处理的文件有多大——如果你处理的是几十或几百KB的文件,你通常应该只写最简单的代码,而不必太担心——你能做的任何事情基本上都是即时的,所以优化代码不会真正起到多大作用。

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

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

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

它在很大程度上取决于上下文,由于代码周围的上下文不存在,所以很难说。

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

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