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

unt*_*ble 6 c++ stringstream

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

我在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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

jxh*_*jxh 5

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

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

elapsed: 0.46 stringstream
elapsed: 0.11 strtod
Run Code Online (Sandbox Code Playgroud)

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

elapsed: 1.685 stringstream
elapsed: 0.171 strtod
Run Code Online (Sandbox Code Playgroud)

我推测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;
}
Run Code Online (Sandbox Code Playgroud)

这是我用过的例行程序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;
}
Run Code Online (Sandbox Code Playgroud)

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

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();
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

而且,这是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");
}
Run Code Online (Sandbox Code Playgroud)


wil*_*ilx 2

您的开销既在于重复实例化,std::stringstream又在于解析本身。如果您的数字很简单并且不使用任何与区域设置相关的格式,那么我建议#include <cstdlib>std::strtod()

  • 或者在 C++11 中,您可以使用 [std::stod](http://en.cppreference.com/w/cpp/string/basic_string/stof) (3认同)
  • @KillianDS:不幸的是......它很可能会更慢。`stod` 从字符串的开头开始解析,因此即使它为您提供了第一个未解析字符的索引,从字符串中弹出已解析的字符以调用 `stod` 会导致内存中的巨大移动。TL;DR:“stod”是白痴。 (2认同)