优化文件读取C ++

时间:2014-01-25 10:11:23

标签: c++ file-io

string str1, str2;
vector<string> vec;

ifstream infile;

infile.open("myfile.txt");
while (! infile.eof() )
{
    getline(infile,str1);
    istringstream is;
        is >> str1;
    while (is >> str2)
    { 
        vec.push_back(str2);
    }
} 

代码所做的是从文件中读取字符串并将其存储到向量中。

表现需要优先考虑。如何优化此代码,使读取性能更快?

4 个答案:

答案 0 :(得分:4)

正如其他人已经指出的那样(例如参见herohuyongtao's answer),必须修复循环条件以及如何将str1放入istringstream

然而,这里有一个重要的问题,到目前为止每个人都错过了:你根本不需要istringstream

  vec.reserve(the_number_of_words_you_exptect_at_least);

  while (infile >> str1) {

    vec.push_back(str1);
  }

它摆脱了你首先不需要的内部循环,并且在每次迭代中都没有创建istringstream

如果你需要进一步解析每一行,你需要一个istringstream,在循环外面创建并通过istringstream::str(const string& s)设置它的字符串缓冲区。

我可以很容易地想象你的循环非常慢:Windows上的堆分配非常慢(与Linux相比);我被咬了一次。

Andrei Alexandrescu(在某种意义上)在他的演讲Writing Quick Code in C++, Quickly中提出了一个类似的例子。令人惊讶的是,在如上所述的紧密循环中执行不必要的堆分配可能比实际文件IO慢。我很惊讶地看到了这一点。


你没有将你的问题标记为C ++ 11,但这是我在C ++ 11中所做的。

  while (infile >> str1) {

    vec.emplace_back(std::move(str1));
  }

此移动在向量的后面构造字符串,而不进行复制。我们可以这样做,因为在将它放入向量之后我们不需要str1的内容。换句话说,不需要将它复制到矢量背面的全新字符串中,只需将其内容移动到那里就足够了。 vec.push_back(str1); 的第一个循环可能可能会复制str1的内容,这实际上是不必要的。

gcc 4.7.2中的字符串实现目前是copy on write,因此两个循环具有相同的性能;你使用哪一个并不重要。现在。

不幸的是,标准现在禁止复制写入字符串。我不知道gcc开发人员什么时候会改变实现。如果实施方式发生变化,无论您是移动(emplace_back(std::move(s)))还是复制(push_back(s)),都可能会对性能产生影响。

如果C ++ 98兼容性对您很重要,请使用push_back()。即使将来最糟糕的事情发生并且您的字符串被复制(现在没有被复制),该副本也可以变成memmove() / memcpy(),这种速度非常快,很可能比从硬盘读取文件的内容,因此文件IO很可能仍然是瓶颈。

答案 1 :(得分:2)

在进行任何优化之前,您需要更改

while (! infile.eof() )      // problem 1
{
    getline(infile,str1);
    istringstream is;
        is >> str1;          // problem 2
    while (is >> str2){ 
        vec.push_back(str2);
        }
 }

while ( getline(infile,str1) ) // 1. don't use eof() in a while-condition
{
    istringstream is(str1);    // 2. put str1 to istringstream
    while (is >> str2){ 
        vec.push_back(str2);
        }
 }

使其按预期工作。


P.S。对于优化部分,除非它成为瓶颈,否则您不需要过多考虑它。 Premature optimization is the root of all evil。但是,如果您想加快速度,请查看@Ali的答案以获取更多信息。

答案 2 :(得分:1)

Loop condition is wrong.不是性能问题。假设这个IO循环确实是你的应用程序的瓶颈。但即使不是,它也可以是一次很好的教育活动,也可以只是周末的乐趣。

你在循环中有很多临时和动态内存分配的情况。

在循环前调用std::vector::reserve()会稍微改善一下。手动重新分配以模拟x1.2增长因子,在某种尺寸后反对2倍也会有所帮助。如果文件大小不可预测,std::list可能更合适。

使用std::istringstream作为标记器非常 inoptimal。切换到基于迭代器的“视图”标记器(Boost has one)应该可以提高速度。

如果您需要快速非常并且有足够的RAM,您可以在读取文件之前对内存进行映射。 Boost::iostreams可以让你快速到达那里。一般来说,如果没有Boost,你可以快两倍(Boost不错,但它必须是通用的,并且可以在十几个编译器上工作)。

如果你是一个使用Unix / Linux的有福的人,你的开发环境在valgrind --tool=cachegrind下运行你的程序,你会看到所有有问题的地方以及它们相对于另一个有多糟糕。此外,valgrind --tool=massif将允许您识别nubmerous小堆分配的对象,这在高性能代码中通常是不可容忍的。

答案 3 :(得分:1)

最快但不完全可移植的方法是将文件加载到内存映射区域(请参阅wiki mmap

鉴于您知道文件的大小,您现在可以在该内存区域上定义前向迭代器(可能是指向const char的指针),您可以使用它来查找将文件分隔为“字符串”的标记。

基本上,你反复得到一对指向第一个字符的指针,分别指向每个“字符串”的结尾。从这对迭代器中创建std::string

这种方法虽然有微妙的问题:

  • 您需要处理文件的字符编码,可能会将此字符编码转换为std::string(可能的UTF-8)所使用的所需编码。

  • 用于分隔字符串的“标记”(通常为\n)可能与平台有关,也可能取决于创建该文件的程序。