【问题标题】:how to speed up ifstream cast to float如何加快 ifstream 转换为浮动
【发布时间】:2013-11-09 08:40:21
【问题描述】:

我正在尝试编写一个相当有效的文件读入例程。 我的数据文件是一个带有几个“框架”的文本文件。每帧有2个标题行和若干项,如下

<int "nitems">
<float> <float> <float>
<string1> <float> <float> <float>
<string2> <float> <float> <float>
...
<string-nitems> <float> <float> <float>

我当前的实现使用 fstream 来检索数字,但看起来非常慢。我的测试文件包含大约 200 帧,每帧 10.000 行 (~75 Mb),需要 2.5 秒才能读取!

int loadframe() {
  _file >> _nat;
  _file >> _cell[0] >> _cell[1] >> _cell[2];
  for(int i=0,k=0;i<_nat;i++) {
    _file >> _types[i] >> _pos[k++] >> _pos[k++] >> _pos[k++]; // this line !!!
  }   
  return 0;
}

_file 是 ifstream(在别处打开),_types 是字符串向量,_cell 和 _pos 是双精度向量。

有没有人建议如何加快速度?

谢谢。

更新 1

fscanf 重写这部分将时间从 ~2.5 秒减少到 ~1.8 秒:大约 30% 增益,还不错。 _f 现在是 FILE* _f = fopen(filename,"r") 对象类型。 fscanf 下面的行用于强制转换(如果需要),但不会占用任何大量时间,正如在注释掉它们时可以看到的那样。

int loadxyz() {
  char c[16];
  float x0,x1,x2;
  fscanf(_f,"%d",&_nat);
  fscanf(_f,"%f%f%f",&x0,&x1,&x2;
  _cell[0]=x1; _cell[1]=x2; _cell[2]=x3;
  for(int i=0, k=0;i<_nat;i++,k+=3) {
    fscanf(_f,"%s%f%f%f",&c,&x0,&x1,&x2);
    _types[i]=c; _pos[k]=x0; _pos[k+1]=x1; _pos[k+2]=x2;
  }
  return 0;
}

更新 2

根据下面的建议我写了一个小的基准程序,这表明 Nim 的解决方案显然最快。在我的情况下,编译器优化没有任何显着影响。对于任何想尝试的人,我在下面添加了来源。需要最新的编译器g++ -std=c++11 readtest.cpp -o readtest

谢谢!如果有人还有其他建议,我将非常乐意添加/对它们进行基准测试。

结果(测试文件约为 32Mb)

$ ./readtest 
write                 : took 1.97 seconds
check = 549755289600.00
read1 (ifstream)      : took 1.10 seconds
check = 549755289600.00
read2 (fscanf)        : took 0.64 seconds
check = 549755289600.00
read3 (stream+strtod) : took 0.41 seconds

这里是readtest.cpp的来源:

#include <stdio.h>    // printf, fopen, fclose, fprintf, 
#include <stdlib.h>   // strtod 
#include <fstream>    // ifstream
#include <string>     // string
#include <ctime>      // clock

#define N 1048576 // 1024*1024 number of lines

using namespace std;

void write(string name) {
  FILE* f = fopen(name.c_str(),"w");
  for(float i=0;i<N;i++) 
    fprintf(f,"%s %.2f %.2f %.2f\n","x",i,i,i); // write some formatted data 
  fclose(f);
}

void read1(string name) {
  double num,check=0;
  string s;
  ifstream f(name);
  for(int i=0;i<N;i++) {
    f >> s;
    f >> num;
    f >> num;
    f >> num;
    check+=num;
  }
  printf("check = %.2f\n",check);
  f.close();
}

void read2(string name) {
  double num,check=0;
  char c[16];
  string s;
  FILE* f=fopen(name.c_str(),"r");
  while(fscanf(f,"%s%lf%lf%lf",&c,&num,&num,&num)!=EOF) {
    s = c;
    check+=num;
  }
  printf("check = %.2f\n",check);
  fclose(f);
}

void read3(string name) {
  string line, s;
  double num,check=0;
  ifstream f(name);
  while(getline(f,line)) {
    size_t start = line.find_first_not_of(" \t");
    size_t pos = line.find(" ");
    char* c = &*(line.begin() + pos + 1); 
    s = line.substr(start,pos+1); 
    num = strtod(c+start, &c);
    num = strtod(c, &c);
    num = strtod(c, &c);
    check+=num;
  }
  printf("check = %.2f\n",check);
  f.close();
}

int main() {
  clock_t start, end;
  string name("testfile.dat");

  start = clock();
  write(name);
  end   = clock();
  printf("write                 : took %.2f seconds\n",double(end-start)/CLOCKS_PER_SEC);

  start = clock();
  read1(name);
  end   = clock();
  printf("read1 (ifstream)      : took %.2f seconds\n",double(end-start)/CLOCKS_PER_SEC);

  start = clock();
  read2(name);
  end   = clock();
  printf("read2 (fscanf)        : took %.2f seconds\n",double(end-start)/CLOCKS_PER_SEC);

  start = clock();
  read3(name);
  end   = clock();
  printf("read3 (stream+strtod) : took %.2f seconds\n",double(end-start)/CLOCKS_PER_SEC);
}

【问题讨论】:

  • 您是否在打开优化的情况下进行编译?
  • fscanf 如果您对速度要求苛刻,则更快
  • 我只是对有问题的行进行了更彻底的研究:无论如何它都有未定义的行为,因为k 在没有干预序列点的情况下多次递增(尽管这可能不是性能问题)。
  • @jaap: kint 类型,并且在不多次干预序列点的情况下递增。这样做会导致未定义的行为。使用在道德上等同于k += ++k;++k + ++k 等。它们都会导致未定义的行为!
  • 我认为瓶颈可能不是ifstream,而可能是你的硬盘。对于 HDD 硬盘驱动器,75MB 需要 2 秒是正常的。

标签: c++ file-io casting fstream


【解决方案1】:

您可以直接拨打strtod。首先使用getline 读入字符串字段中的整行。然后找到第一个空格,记住位置。从这里直接调用strtod 三次,然后将字符串修剪到第一个空格。不漂亮,但可以更快。

类似这样的:

auto& s = _types[i];
std::getline(_file, s);
auto pos = s.find(" ");
auto c = &*(s.begin() + pos + 1); // data() returns a const, this workaround should do the trick
_pos.push_back(std::strtod(c, &c));
_pos.push_back(std::strtod(c, &c));
_pos.push_back(std::strtod(c, nullptr)); // last one doesn't need it

顺便说一句:你的代码效率有点低..

while(getline(f,item)) {
  size_t start = item.find_first_not_of(" \t"); // skip initial whitespace
  size_t pos = item.find(' ', start); // use the above find...
  char* c = &*(item.begin() + pos + 1);
  num = strtod(c, &c);
  num = strtod(c, &c);
  num = strtod(c, NULL); // last one we don't care..
  item.erase(0, start);  // trim the white space at start
  item.erase(pos);       // trim only to the first word that you want...
  check+=num;
}

上面的应该效率高一点……

【讨论】:

  • 然而,从一个最小的示例中,我发现 strtod 确实应该比分析器中的结果快得多(10 倍)。我会回到这个。
  • 您还应该在向量上调用 reserve 以防止通过运行分配...
  • 谢谢。对于向量,我实际上是在阅读大小后在某处调整大小。我想我可以通过使用 reserve 然后 push_back 来获得一些东西,但是调整大小的成本可以忽略不计。只是一个问题,要编译这个我需要'g++ -std=c++11 -fpermissive`,但我仍然收到一些关于invalid conversion from ‘const char**’ to ‘char**’的警告,有没有更标准的方法解决这个问题?
  • 您能否更详细地解释一下这是如何工作的?我更新了我的帖子并包含了您的代码的(混蛋?)变体。据我所知,指针“c”在 strtod 期间更新,这就是它每次跳转到下一个项目的原因。那是对的吗?无论如何,这很聪明。如果可能的话,摆脱编译器警告也会很好。
  • 您只需要 c++11,因为我使用的是 std::strtodauto,c++03 也有可用的变体。查看更新的答案,您在strtod 上是正确的,该函数将第二个参数指向的指针设置为指向最后一个已解析字符之后的字符。
【解决方案2】:

在我看来,您可以尝试在 ifstream 和其他方法(同步、区域设置处理和提高优化级别以及其他 gcc 标志)中使用更大的缓冲区,如下所述:

https://stackoverflow.com/a/5168384/866930

或者,或者,并且有争议地,使用fscanf

【讨论】:

  • 查看配置文件,似乎fscanf() 确实是上述设置的更好选择:与在std::num_get&lt;...&gt; 中花费的时间相比,在std::num_get&lt;...&gt; 中花费的时间太多strtod() 实际转换发生的地方。在使用的库中,std::num_get&lt;...&gt; 的实现可能有一些方法可以改进。
  • @DietmarKühl 谢谢,我添加了配置文件也是因为我没有解释这个的经验。正如我从您的评论中了解到的那样,fscanf 至少应该允许我将这 37%(在 std:num_get<...> 中的支出)减少到接近于零的水平。
  • 我尝试了缓冲区,但没有发现任何明显的改进。
猜你喜欢
  • 1970-01-01
  • 2016-05-27
  • 2018-03-29
  • 1970-01-01
  • 2021-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多