C++字符串流速度太慢,如何提高速度?

25

可能是重复问题:
使用C++从文本文件中快速读取数值(此例中为double)的最快方法

#include <ctime>
#include <cstdlib>
#include <string>
#include <sstream>
#include <iostream>
#include <limits>

using namespace std;

static const double NAN_D = numeric_limits<double>::quiet_NaN();

void die(const char *msg, const char *info)
{
    cerr << "** error: " << msg << " \"" << info << '\"';
    exit(1);
}

double str2dou1(const string &str)
{
    if (str.empty() || str[0]=='?') return NAN_D;
    const char *c_str = str.c_str();
    char *err;
    double x = strtod(c_str, &err);
    if (*err != 0) die("unrecognized numeric data", c_str);
    return x;
}

static istringstream string_to_type_stream;

double str2dou2(const string &str)
{
    if (str.empty() || str[0]=='?') return NAN_D;
    string_to_type_stream.clear();
    string_to_type_stream.str(str);
    double x = 0.0;
    if ((string_to_type_stream >> x).fail())
        die("unrecognized numeric data", str.c_str());
    return x;
}

int main()
{
    string str("12345.6789");

    clock_t tStart, tEnd;

    cout << "strtod: ";
    tStart=clock();

    for (int i=0; i<1000000; ++i)
        double x = str2dou1(str);

    tEnd=clock();
    cout << tEnd-tStart << endl;

    cout << "sstream: ";
    tStart=clock();

    for (int i=0; i<1000000; ++i)
        double x = str2dou2(str);

    tEnd=clock();
    cout << tEnd-tStart << endl;

    return 0;
}

strtod: 405
sstream: 1389

update: 去掉下划线,环境为win7+vc10


尝试使用boost::spirit代替。 - Sergey Miryanov
1
那些双下划线的名称在用户编写的代码中是非法的。如果stringstream对你来说太慢了 - 你有答案了 - 使用strtod。stringstream主要是为了方便和类型安全,而不是速度。 - user2100815
这是哪个编译器?也许使用STL的stlport实现比自带的更快(尽管不可能超过strtod)。 - Jan Hudec
@unapersson 双下划线的名称是从其他地方复制过来的,懒得修改它们。 - hjbreg
@hjbreg:仅仅因为一个函数比另一个函数慢,并不能解释你为什么认为它“太慢”——你真的需要它更快吗? - Doc Brown
显示剩余4条评论
4个回答

12

C/C++中的文本转数字格式非常缓慢。流非常缓慢,但即使是C数字解析也很慢,因为它很难正确到最后一位精度。在一个注重读取速度且数据最多有三个小数位并且没有科学计数法的生产应用程序中,我通过手写浮点解析函数来实现了巨大的改进,该函数只处理符号、整数部分和任意数量的小数部分("巨大"的意思是与strtod相比快10倍)。

如果您不需要指数,并且此功能的精度已经足够,那么这是类似于我当时编写的解析器的代码。在我的PC上,它现在比strtod快6.8倍,比sstream快22.6倍。

double parseFloat(const std::string& input)
{
    const char *p = input.c_str();
    if (!*p || *p == '?')
        return NAN_D;
    int s = 1;
    while (*p == ' ') p++;

    if (*p == '-') {
        s = -1; p++;
    }

    double acc = 0;
    while (*p >= '0' && *p <= '9')
        acc = acc * 10 + *p++ - '0';

    if (*p == '.') {
        double k = 0.1;
        p++;
        while (*p >= '0' && *p <= '9') {
            acc += (*p++ - '0') * k;
            k *= 0.1;
        }
    }
    if (*p) die("Invalid numeric format");
    return s * acc;
}

7

字符串流非常慢。非常非常慢。如果您正在编写任何处理大型数据集(例如在游戏中更改级别后加载资产)的性能关键代码,不要使用字符串流。我建议使用旧式的C库解析函数以获得更好的性能,尽管我无法确定它们与像boost spirit这样的东西相比如何。

然而,与C库函数相比,字符串流非常优雅、易读且可靠,因此如果您所做的不是性能关键操作,我建议继续使用流。


5
一般来说,如果你需要速度,可以考虑使用这个库:http://www.fastformat.org/。(不过我不确定它是否包含将字符串或流转换为其他类型的函数,所以它可能无法解决您当前的问题。)
请注意,您在这里比较了两个不同的东西。strtod()是一个简单的函数,只有一个目的(将字符串转换为double),而stringstream是一个更复杂的格式化机制,远未被优化到特定的目的。一个更公正的比较应该是将stringstream与sprintf/sscanf系列函数进行比较,这些函数比strtod()慢但比stringstream快。我不太确定是什么使得stringstream的设计比sprintf/sscanf慢,但似乎情况确实如此。

1
为什么STL比FastFormat慢? - hjbreg
6
@hjbreg,因为它需要支持本地化。 - Alex B
@hjbreg:这有几个原因。其中一部分可能与流设计考虑和未经优化的实现有关。另一个原因是STL的灵活性,这可能包括区域设置支持和IO操作符支持(我不确定fastformat是否具有这些或类似的功能)。 - Boaz Yaniv
1
strtod 并不是特别简单。它处理区域设置(例如千位分隔符与小数点,以及对千位分隔符的挑剔),并且非常小心地进行四舍五入。使 iostreams 变慢的是疯狂的虚函数调度级别,以及导致实现者完全放弃任何优化的正确性难题。 - Potatoswatter
@hjbreg:fastformat支持本地化(除了大量的喊叫宣传之外) - sehe

2

1
+1 for lexical_cast 但是由于它在底层使用了字符串流,所以速度不会更快。 - Konrad Rudolph
1
在我的测试中,lexical_cast 太慢了。 - Sergey Miryanov
3
"lexical_cast" 是使用 "stringstream" 实现的。;-) - Jasper Bekkers
1
boost lexical_cast 比 std::stringstream 快得多。这是性能比较:http://www.boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/performance.html - J-Mik
1
@JasperBekkers 只在后备情况下使用。它专门针对许多原始类型。 - sehe
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接