Confrontando 3 moderni modi c ++ per convertire valori integrali in stringhe

Stavo cercando di scegliere un modo standard per convertire gli integrali in stringhe , quindi ho proseguito e ho fatto una piccola valutazione delle prestazioni misurando il tempo di esecuzione di 3 metodi

#include  #include  #include  #include  #include  #include  #include  #include  #include  using namespace std; // 1. A way to easily measure elapsed time ------------------- template struct measure { template static typename TimeT::rep execution(F const &func) { auto start = std::chrono::system_clock::now(); func(); auto duration = std::chrono::duration_cast( std::chrono::system_clock::now() - start); return duration.count(); } }; // ----------------------------------------------------------- // 2. Define the convertion functions ======================== template // A. Using stringstream ================ string StringFromNumber_SS(T const &value) { stringstream ss; ss << value; return ss.str(); } template // B. Using boost::lexical_cast ========= string StringFromNumber_LC(T const &value) { return boost::lexical_cast(value); } template // 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 long long MeasureExec(std::vector 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 std::enable_if<std::is_integral::value>::type FillVec(vector &v) { std::mt19937 e2(1); std::uniform_int_distribution dist(3, 1440); std::generate(v.begin(), v.end(), [&]() { return dist(e2); }); } template typename std::enable_if<!std::is_integral::value>::type FillVec(vector &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 v1(991908); FillVec(v1); cout << "C++ 11 method ......... " << MeasureExec(v1, StringFromNumber_C11) << endl; cout << "String stream method .. " << MeasureExec(v1, StringFromNumber_SS) << endl; cout << "Lexical cast method ... " << MeasureExec(v1, StringFromNumber_LC) << endl; return 0; } 

Un tipico output (esecuzione di Release in VS2013 che implica / O2 ottimization))

Metodo C ++ 11 ……… 273

Metodo del stream di stringa .. 1923

Metodo di fusione lessicale … 222

AGGIORNARE

In alternativa una corsa online su gcc con

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

Metodo C ++ 11 ……… 414

Metodo del stream di stringa .. 1538

Metodo di fusione lessicale … 275

Disclaimer: i risultati devono essere confrontati tra loro e non tra le macchine

Domande

1. Perché il metodo del stream di stringa è sempre il peggiore (di un ordine di grandezza)? Dovrebbe essere considerato deprecato ora che sono emerse alternative più veloci?

2. Perché il cast lessicale è sempre il migliore? Possiamo supporre che questa sia l’implementazione più veloce?

Sentiti libero di modificare e giocare con le tue versioni di questo codice. Apprezzerei le tue opinioni sull’argomento.

PS

Il codice che è stato effettivamente eseguito, aveva solo una misura per main() . Qui tutti erano 3 sono stati presentati insieme per risparmiare spazio.

I flag di ottimizzazione sono specifici del compilatore o richiesti dall’applicazione. Sto solo fornendo i blocchi di codice per eseguire i test e mi aspetto che gli utenti SO possano tramezzare con i loro risultati o suggerimenti su quale sia la configurazione ottimale per compilatore (per quello che vale ho fornito i flag usati qui).

Il codice funziona per qualsiasi conversione da numerico a stringa (è necessario modificare il tipo di v1 in main ). sehe ha fatto per il double (menzionato nel commento della sua risposta). È una buona idea giocare anche con questo.

Domanda 1 . Perché il metodo del stream di stringa è sempre il peggiore ?

L’errore classico: creare un nuovo stringstream ogni volta

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

Domanda 2 . Perché il cast lessicale è sempre il migliore? Possiamo supporre che questa sia l'implementazione più veloce?

Perché è il più specializzato; e, no, esistono implementazioni più veloci. FastFormat e Boost Spirit hanno offerte competitive, per quanto ne so.

Aggiorna Boost Spirit Karma batte ancora facilmente il gruppo:

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

Tempi:

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

Guardalo dal vivo su Coliru Clang o GCC


BONUS

Solo per scherzare, una versione che utilizza boost::string_ref è molto più veloce a causa delle ridotte allocazioni:

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

Ho testato tutti i metodi modificati per la correttezza utilizzando un ciclo di prova assertivo:

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