CPP:解析字符串流太慢

CPP : Parsing String Stream is too slow

本文关键字:字符串 CPP      更新时间:2023-10-16

我的cpp代码需要读取一个7 MB的文本文件的空格分隔浮动值。将字符串值解析为浮点数组大约需要6秒,这对于我的用例来说太多了。

我一直在网上查,人们说通常是物理IO需要时间。为了消除这种情况,我一次将文件读取到字符串流中,并将其用于浮点解析。代码速度仍然没有提高。有什么办法让它跑得更快吗?

下面是我的代码(为了简单起见,用dummy_f替换了数组项):
    #include "stdafx.h"
    #include <iostream>
    #include <fstream>
    #include "time.h"
    #include <sstream>
    using namespace std;
    int main()
    {
      ifstream testfile;
      string filename = "test_file.txt";
      testfile.open(filename.c_str());
      stringstream string_stream;
      string_stream << testfile.rdbuf();
      testfile.close();
      clock_t begin = clock();
      float dummy_f;
      cout<<"started stream at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;
      for(int t = 0; t < 6375; t++)
      {
           string_stream >> dummy_f;
           for(int t1 = 0; t1 < 120; t1++)
           {
               string_stream >> dummy_f;
           }
      }
      cout<<"finished stream at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;
      string_stream.str("");
      return 0;
     } 
编辑:

test_cases.txt文件的链接https://drive.google.com/file/d/0BzHKbgLzf282N0NBamZ1VW5QeFE/view?usp=sharing

请在运行此文件时将内循环尺寸更改为128(打了个错字)

编辑:我找到了一个办法。将dummy_f声明为字符串,并将其作为字符串字从stringstream中读取。然后使用atof将字符串转换为浮点数。时间为0.4秒,这对我来说已经足够好了。

  string dummy_f;
  vector<float> my_vector;
  for(int t = 0; t < 6375; t++)
  {
       string_stream >> dummy_f;
       my_vector.push_back(atof(dummy_f.c_str()));
       for(int t1 = 0; t1 < 128; t1++)
       {
           string_stream >> dummy_f;
            my_vector.push_back(atof(dummy_f.c_str()));
       }
  }

update:在@Mats的评论中讨论得出的结论是,锁定开销不太可能与此有关,所以我们回到了原点,以解释为什么Visual c++的库在解析浮点数时如此缓慢。您的样例测试文件看起来主要是数量级不太接近1.0的数字,没有什么奇怪的事情发生。(英特尔在Sandybridge的FPU;根据阿格纳·福格的表格,"后来"对变态没有惩罚。

正如其他人所说,现在是时候分析代码并找出哪个函数占用了所有CPU时间。此外,性能计数器可以告诉您分支错误预测或缓存丢失是否会导致问题。


每次调用cin >> dummy_f都需要锁定,以确保其他线程不会同时修改输入缓冲区。用scanf("%f%f%f%f", &dummy_array[0], &dummy_array[1], ...)一次读取4或8个浮点数会更有效率一点,如果这是瓶颈所在的话。(scanf也不是一个很好的API,因为它需要每个数组元素的地址作为函数参数。但是,通过在一次扫描中使用多个转换展开仍然是一个小小的性能优势。

你试图用stringstream来解决这个问题,这可能有效,也可能无效。它是函数中的一个局部变量,所以如果编译器可以看到所有的函数并内联它们,它就不必为锁而烦恼了。不能有其他线程访问这个变量

在我的Linux机器上,它只需要<0.3秒,所以如果OP没有在调试/发布构建时出错,那么它应该是Windows独有的问题:

hidden$ cat read-float.cpp 
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;
int main() {
  ifstream fs("/tmp/xx.txt");
  vector<float> v;
  for (int i = 0; i < 6375; i++) {
    for (int j = 0; j < 129; j++) {
      float f;
      fs >> f;
      v.emplace_back(f);
    }
  }
  cout << "Read " << v.size() << " floats" << endl;
}
hidden$ g++ -std=c++11 read-float.cpp -O3
hidden$ time ./a.out 
Read 822375 floats
real    0m0.287s
user    0m0.279s
sys 0m0.008s
hidden$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04) 

使用运行速度快3倍的atof的替代实现粘贴在下面。在我的笔记本电脑上,原始的基于stringstream的程序需要2.3秒才能完成,而对于相同数量的浮点数,这个程序在0.8秒内完成。

static char filecontents[10*1024*1024];
int testfun2()
{
  ifstream testfile;
  string filename = "test_file.txt";
  testfile.open(filename.c_str());
  int numfloats=0;
  testfile.read(filecontents,10*1024*1024);
  size_t numBytesRead = testfile.gcount();
  filecontents[numBytesRead]='';
  testfile.close();
  clock_t begin = clock();
  float dummy_f;
  cout<<endl<<"started at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;
  char* p= filecontents;
  char* pend = p + numBytesRead;
  while(p<pend)
  {
      while(*p && (*p <= ' '))
      {
         ++p; //skip leading white space ,r, n
      }
      char* pvar = p;
      while(*p > ' ')
      {
        ++p; //skip over numbers
      }
      if(*p)
      {  *p = '';// shorter input makes atof faster.
        ++p;
      }
      if(*pvar)
      {
         dummy_f = atof(pvar);
         ++numfloats;
      }
      //cout << endl << dummy_f;
  }
  cout<<endl<< "finished at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;
  cout << endl << "numfloats= " << numfloats;
  return numfloats;
 }