为什么C ++中字符串到数字转换的速度如此之慢?

时间:2012-07-12 05:51:33

标签: c++ stringstream

此函数从字符串中读取双精度数组:

vector<double> parseVals(string& str) {
    stringstream ss(str);
    vector<double> vals;
    double val;
    while (ss >> val) vals.push_back(val);
    return vals;
}

当使用包含100万个数字的字符串调用时,该函数需要7.8秒才能执行(Core i5,3.3GHz)。这意味着花费25000个CPU周期来解析一个数字。

user315052已经指出相同的代码在他的系统上运行速度快了一个数量级,并且进一步的测试显示了不同系统和编译器之间的非常大的性能差异(另见user315052的答案):

1. Win7, Visual Studio 2012RC or Intel C++ 2013 beta: 7.8  sec
2. Win7, mingw / g++ 4.5.2                          : 4    sec
3. Win7, Visual Studio 2010                         : 0.94 sec
4. Ubuntu 12.04, g++ 4.7                            : 0.65 sec

我在Boost / Spirit库中找到了一个很好的选择。代码安全,简洁,速度极快(VC2012上为0.06秒,比stringstream快130倍)。

#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

vector<double> parseVals4(string& str) {
    vector<double> vals;
    qi::phrase_parse(str.begin(), str.end(),
        *qi::double_ >> qi::eoi, ascii::space, vals);
    return vals;
}

虽然这从实际角度解决了这个问题,但我仍然想知道为什么stringstream的性能如此不一致。我描述了该程序来识别瓶颈,但STL代码看起来像是胡言乱语。任何熟悉STL内部人员的评论都会非常感激。

PS:在上述所有时间中优化均为O2或更高。既不是字符串流的实例化,也不是程序配置文件中矢量图的重新分配。实际上,所有的时间都花在了提取操作员身上。

5 个答案:

答案 0 :(得分:5)

在运行1.6 GHz i7的Linux VM上,只需不到半秒钟。我的结论是解析并不像你观察它那么慢。你必须测量一些其他的神器,以使你的观察与我的观察有很大的不同。因此,我们可以更加确定我们将苹果与苹果进行比较,我会提供我所做的。

编辑:在我的Linux系统上,我有g++ 4.6.3,使用-O3编译。由于我没有MS或英特尔编译器,我使用了cygwin g++ 4.5.3,也编译了-O3。在Linux上,我得到了以下输出:另一个事实是我的Windows 7是64位,就像我的Linux VM一样。我相信cygwin只能以32位模式运行。

elapsed: 0.46 stringstream
elapsed: 0.11 strtod

在cygwin上,我得到了以下内容:

elapsed: 1.685 stringstream
elapsed: 0.171 strtod

我推测cygwin和Linux性能之间的区别与MS库依赖关系有关。请注意,cygwin环境就在Linux VM的主机上。

这是我使用istringstream时的例行程序。

std::vector<double> parseVals (std::string &s) {
    std::istringstream ss(s);
    std::vector<double> vals;
    vals.reserve(1000000);
    double val;
    while (ss >> val) vals.push_back(val);
    return vals;
}

这是我使用strtod时的例行程序。

std::vector<double> parseVals2 (char *s) {
    char *p = 0;
    std::vector<double> vals;
    vals.reserve(1000000);
    do {
        double val = strtod(s, &p);
        if (s == p) break;
        vals.push_back(val);
        s = p+1;
    } while (*p);
    return vals;
}

这是我用来填充一百万个双打的字符串的例程。

std::string one_million_doubles () {
    std::ostringstream oss;
    double x = RAND_MAX/(1.0 + rand()) + rand();
    oss << x;
    for (int i = 1; i < 1000000; ++i) {
        x = RAND_MAX/(1.0 + rand()) + rand();
        oss << " " << x;
    }
    return oss.str();
}

这是我用来做时间的例程:

template <typename PARSE, typename S>
void time_parse (PARSE p, S s, const char *m) {
    struct tms start;
    struct tms finish;
    long ticks_per_second;
    std::vector<double> vals_vec;

    times(&start);
    vals_vec = p(s);
    times(&finish);
    assert(vals_vec.size() == 1000000);
    ticks_per_second = sysconf(_SC_CLK_TCK);
    std::cout << "elapsed: "
              << ((finish.tms_utime - start.tms_utime
                   + finish.tms_stime - start.tms_stime)
                  / (1.0 * ticks_per_second))
              << " " << m << std::endl;
}

而且,这是main函数:

int main ()
{
    std::string vals_str;

    vals_str = one_million_doubles();
    std::vector<char> s(vals_str.begin(), vals_str.end());

    time_parse(parseVals, vals_str, "stringstream");
    time_parse(parseVals2, &s[0], "strtod");
}

答案 1 :(得分:2)

您的开销是std::stringstream的重复实例化和解析本身。如果您的数字是普通的并且没有使用任何与语言环境相关的格式,那么我建议#include <cstdlib>std::strtod()

答案 2 :(得分:1)

string转换为double的速度很慢,因为您的Corei5 CPU没有内置转换运算符。

虽然该CPU本身可以以相对较快的速度将short转换为floatint,但您描述的转换必须一步一步地完成,分析每个字符并确定它是否属于double以及如何。

您正在观察的内容代表了需要完成的实际工作,考虑到每个双方可能看起来像-.0INF或{{1} }或4E6。它可能需要被截断,它可能需要近似,它可能根本不是有效的-NAN

答案 3 :(得分:0)

这是一个非常复杂的解析任务。要解析double的两个必须匹配小数或浮点数,那么它必须提取此字符串并执行实际的字符串转换。这意味着对于你的字符串中的每个double,你要经历每个double至少两次以及为了获得下一个double而执行的任何其他功能。提到的另一部分是调整大小时的向量不是最有效的。但是,解析和转换字符串的速度很慢。

答案 4 :(得分:0)

每次调用该函数时都会构造一个stringstream对象,这可能非常昂贵。

但是,我们没有足够的信息来回答您的问题。您是否正在编译并一直开启优化?你的函数是内联的,还是每个调用都有一个函数调用?

有关如何加快速度的建议,您应该考虑boost::lexical_cast<double>(str)