6

这个函数从一个字符串中读取一个双精度数组:

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 或更好。程序配置文件中既不是字符串流的实例化,也不是矢量图的重新分配。几乎所有的时间都花在提取操作符内部。

4

5 回答 5

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

在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");
}
于 2012-07-12T06:45:33.480 回答
2

您的开销在于重复实例化std::stringstream和解析本身。如果您的数字是纯数字并且不使用任何依赖于语言环境的格式,那么我建议使用#include <cstdlib>std::strtod().

于 2012-07-12T06:02:32.770 回答
1

转换stringdouble很慢,因为您的 Corei5 CPU 没有内置转换运算符。

虽然该 CPU 本身可以以相对更快的速度将 a 转换为shorta ,但您描述的转换必须逐步完成,分析每个字符并确定它是否是 the 的一部分以及如何。floatintdouble

考虑到每个替身可能看起来像-.0orINF4E6or ,您所观察到的代表了需要完成的实际工作-NAN。它可能需要被截断,它可能需要近似并且它可能根本不是有效double的。

于 2012-07-12T06:16:10.860 回答
0

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

但是,我们没有足够的信息来回答您的问题。您是否在一直打开优化的情况下进行编译?您的函数是内联的,还是每次调用都有函数调用?

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

于 2012-07-13T04:01:44.950 回答
0

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

于 2012-07-12T06:09:34.623 回答