Сравнение трех современных методов С++ для преобразования целочисленных значений в строки
Я пытался выбрать стандартный способ преобразования интегралов в строки, поэтому я продолжил и сделал небольшую оценку производительности измерение времени выполнения 3 методов
#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;
}
A типичный вывод (запуск Release в VS2013, который подразумевает флаг /O 2 optimizaiton), будет
Метод С++ 11......... 273
Метод струнного потока. 1923
Лексический метод литья... 222
UPDATE
В качестве альтернативы онлайновый запуск gcc с
g++ -std=c++11 -Ofast -march=native -Wall -pedantic main.cpp && ./a.out
С++ 11 метод......... 414
Метод струнного потока. 1538
Лексический метод литья... 275
Отказ от ответственности: результаты должны сравниваться между собой, а не между машинами.
Вопросы
1. Почему метод строкового потока последовательно худший (на порядок)? Должен ли он считаться устаревшим сейчас, когда появились более быстрые альтернативы?
2. Почему лексический литье последовательно лучше всего? Можно ли предположить, что это самая быстрая реализация?
Пожалуйста, не стесняйтесь настраивать и воспроизводить ваши версии этого кода. Буду признателен за эту тему.
PS
Код, который был фактически запущен, имел только одно измерение за main()
. Здесь все были представлены вместе, чтобы сэкономить место.
Флаги оптимизации - это спецификация компилятора или приложение. Я просто предоставляю блоки кода для выполнения тестов и ожидаю от SO-пользователей чипа с их результатами или предложениями о том, какая оптимальная конфигурация для каждого компилятора (для чего это стоило, я предоставил используемые здесь флаги).
Код работает для преобразования числовых строк в (требуется изменить тип v1
в main
). sehe сделал для double
(упомянутый в его комментарии к ответу). Это тоже неплохо поиграть с этим.
Ответы
Ответ 1
Вопрос 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 имеют конкурентные предложения.
Обновление Boost Spirit Karma по-прежнему легко бьет кучу:
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 Clang или GCC
BONUS
Просто, чтобы отключиться, версия, использующая 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)); }});