比较3种现代c ++方法将积分值转换为字符串

时间:2014-05-02 22:18:32

标签: c++ c++11 boost stl

我试图挑选一个standard way to convert integrals to strings,所以我继续measuring the execution time of 3 methods

进行了一次小的效果评估
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <chrono>
#include <random>
#include <exception>
#include <type_traits>
#include <boost/lexical_cast.hpp>

using namespace std;

// 1. A way to easily measure elapsed time -------------------
template<typename TimeT = std::chrono::milliseconds>
struct measure
{
    template<typename F>
    static typename TimeT::rep execution(F const &func)
    {
        auto start = std::chrono::system_clock::now();
        func();
        auto duration = std::chrono::duration_cast< TimeT>(
            std::chrono::system_clock::now() - start);
        return duration.count();
    }
};
// -----------------------------------------------------------

// 2. Define the convertion functions ========================
template<typename T> // A. Using stringstream ================
string StringFromNumber_SS(T const &value) {
    stringstream ss;
    ss << value;
    return ss.str();
}

template<typename T> // B. Using boost::lexical_cast =========
string StringFromNumber_LC(T const &value) {
    return boost::lexical_cast<string>(value);
}

template<typename T> // C. Using c++11 to_string() ===========
string StringFromNumber_C11(T const &value) {
    return std::to_string(value);
}
// ===========================================================

// 3. A wrapper to measure the different executions ----------
template<typename T, typename F>
long long MeasureExec(std::vector<T> const &v1, F const &func)
{
    return measure<>::execution([&]() {
        for (auto const &i : v1) {
            if (func(i) != StringFromNumber_LC(i)) {
                throw std::runtime_error("FAIL");
            }
        }
    });
}
// -----------------------------------------------------------

// 4. Machinery to generate random numbers into a vector -----
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type 
FillVec(vector<T> &v)
{
    std::mt19937 e2(1);
    std::uniform_int_distribution<> dist(3, 1440);
    std::generate(v.begin(), v.end(), [&]() { return dist(e2); });
}

template<typename T>
typename std::enable_if<!std::is_integral<T>::value>::type 
FillVec(vector<T> &v)
{
    std::mt19937 e2(1);
    std::uniform_real_distribution<> dist(-1440., 1440.);
    std::generate(v.begin(), v.end(), [&]() { return dist(e2); });
}
// -----------------------------------------------------------

int main()
{
    std::vector<int> v1(991908);
    FillVec(v1);

    cout << "C++ 11 method ......... " <<
        MeasureExec(v1, StringFromNumber_C11<int>) << endl;
    cout << "String stream method .. " <<
        MeasureExec(v1, StringFromNumber_SS<int>) << endl;
    cout << "Lexical cast method ... " <<
        MeasureExec(v1, StringFromNumber_LC<int>) << endl;

    return 0;
}

典型输出(在VS2013中运行版本意味着/ O2优化标志)将是

  

C ++ 11方法......... 273

     

字符串流方法.. 1923

     

词汇演员方法...... 222

更新

或者使用

online run on gcc
g++ -std=c++11 -Ofast -march=native -Wall -pedantic main.cpp && ./a.out
  

C ++ 11方法......... 414

     

字符串流方法.. 1538

     

词法演员方法...... 275

免责声明:结果将相互比较,而不是跨机器进行比较

问题

1。为什么字符串流方法始终是最差的(按一个数量级)?是否应该将其视为已弃用,以便更快出现更快的替代方案?

2。为什么词汇演员一直是最好的?我们可以假设这是最快的实施吗?

请随意调整并使用此代码的版本。我很感激您对该主题的见解。

PS

实际运行的代码每main()只有一个度量值。为了节省空间,这里共有3个被提出来。

优化标志是特定于编译器或应用程序强制的。我只是提供代码块来执行测试,并期望SO用户将结果或建议插入每个编译器的最佳配置(为此我提供了这里使用的标志值)。

该代码适用于任何数字到字符串转换(需要更改v1main的类型)。 sehe为double做了(在他的回答评论中提到)。玩这个也是个好主意。

1 个答案:

答案 0 :(得分:31)

  

问题1 为什么字符串流方法始终是最差的

经典错误:每次都创建一个新的字符串流

template<typename T> // 1. Using stringstream
string StringFromIntegral_SS(T const &value) {
    thread_local stringstream ss;
    ss.str("");
    ss.clear();
    ss << value;
    return ss.str();
}
  

问题2 为什么词汇演员一直是最好的?我们可以假设这是最快的实现吗?

因为它最专业;并且,不,存在更快的实现。据我所知,FastFormat和Boost Spirit有竞争力的产品。

更新提升精神卡玛仍然很容易击败群体:

template<typename T> // 4. Karma to string
std::string StringFromIntegral_K(T const &value) {
    thread_local auto const gen = boost::spirit::traits::create_generator<T>::call();
    thread_local char buf[20];
    char* it = buf;
    boost::spirit::karma::generate(it, gen, value);
    return std::string(buf, it);
}

时序:

C++ 11 method 111
String stream method 103
Lexical cast method 57
Spirit Karma method 36
Spirit Karma method with string_ref 13

查看 Live On Coliru ClangGCC


奖金

为了解决这个问题,使用boost::string_ref的版本要快得多,原因是分配减少了:

template<typename T> // 5. Karma to string_ref
boost::string_ref StringFromIntegral_KSR(T const &value) {
    thread_local auto const gen = boost::spirit::traits::create_generator<T>::call();
    thread_local char buf[20];
    char* it = buf;
    boost::spirit::karma::generate(it, gen, value);
    return boost::string_ref(buf, it-buf);
}

我使用断言测试循环测试了所有修改过的方法的正确性:

return measure<>::execution(
    //[&]() { for (auto const &i : v1) { func(i); }});
    [&]() { for (auto const &i : v1) { assert(func(i) == StringFromIntegral_LC(i)); }});