C++ Convert String to Double Speed
(There is also a string-to-int performance test.)
A performance benchmark of which method is faster of converting an std::string to a double. The goal is ending up with a double of the value represented in an std::string.
The tested methods are:
- a hand-written naive loop
- atof()
- strtod()
- sscanf()
- boost::lexical_cast<double>()
- boost::spirit::qi::parse()
- std::stringstream
- std::stringstream, reusing the object
Source for the test is at speed-string-to-double.cpp with cycle.h.
The compilers are Microsoft Visual C++ 2010 with _SECURE_SCL disabled, GNU g++ 4.6.0, and LLVM clang++ from Arch.
Tests were run for converting 100000 string containing doubles in the range +/- 99999.99999. The result for the naive loop and atof() are set as the baseline 100% and the other numbers is time spent relative to those. The naive loop wins by a large margin, but Boost.Spirit is the fastest correct implementation.
Windows: MSVC++ 2010
- Compiler: MSVC++ 2010 _SECURE_SCL=0
- Arch: Windows 7 64 bit, 1.60GHz Core i7 Q720, 8 GiB RAM
VC++ 2010 | Ticks | Relative to naive | Relative to atof() |
---|---|---|---|
naive | 4366220 | 1.00 | 0.05 |
atof() | 82732774 | 18.95 | 1.00 |
strtod() | 83189198 | 19.05 | 1.01 |
sscanf() | 168568387 | 38.61 | 2.04 |
spirit qi | 18932917 | 4.34 | 0.23 |
lexical_cast | 332374407 | 76.12 | 4.02 |
stringstream | 361943816 | 82.90 | 4.37 |
stringstream reused | 240848392 | 55.16 | 2.91 |
Linux: GNU g++ 4.6.0
- Compiler: GNU g++ 4.6.0 -O3
- Arch: VirtualBox on the Windows machine, VT-x, Arch Linux, kernel 2.6.38-ARCH, 1 GiB RAM
g++ 4.6.0 | Ticks | Relative to naive | Relative to atof() |
---|---|---|---|
naive | 4656159 | 1.00 | 0.15 |
atof() | 30605490 | 6.57 | 1.00 |
strtod() | 30963926 | 6.65 | 1.01 |
sscanf() | 56235197 | 12.08 | 1.84 |
spirit qi | 20731062 | 4.45 | 0.68 |
lexical_cast | 139521406 | 29.96 | 4.56 |
stringstream | 184723298 | 39.67 | 6.04 |
stringstream reused | 100905407 | 21.67 | 3.30 |
Linux: LLVM clang++ 2.9
- Compiler: clang++ 2.9 -O3
- Arch: VirtualBox on the Windows machine, VT-x, Arch Linux, kernel 2.6.38-ARCH, 1 GiB RAM
clang++ 2.9 | Ticks | Relative to naive | Relative to atof() |
---|---|---|---|
naive | 6804881 | 1.00 | 0.22 |
atof() | 30829865 | 4.53 | 1.00 |
strtod() | 30871514 | 4.54 | 1.00 |
sscanf() | 57903993 | 8.51 | 1.88 |
spirit qi | 24411041 | 3.59 | 0.79 |
lexical_cast | 149339833 | 21.95 | 4.84 |
stringstream | 191239066 | 28.10 | 6.20 |
stringstream reused | 100461405 | 14.76 | 3.26 |
#ifdef _MSC_VER
#define _SECURE_SCL 0
#define _CRT_SECURE_NO_DEPRECATE 1
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define NOMINMAX
#endif #include <cstdlib>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <sstream>
#include <boost/lexical_cast.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include "cycle.h" static const size_t N = ;
static const size_t R = ; void PrintStats(std::vector<double> timings) {
double fastest = std::numeric_limits<double>::max(); std::cout << std::fixed << std::setprecision();
std::cout << "[";
for (size_t i = ; i<timings.size()- ; ++i) {
fastest = std::min(fastest, timings[i]);
std::cout << timings[i] << ",";
}
std::cout << timings.back();
std::cout << "]"; double sum = 0.0;
for (size_t i = ; i<timings.size() ; ++i) {
sum += timings[i];
}
double avg = sum / static_cast<double>(timings.size()-); sum = 0.0;
for (size_t i = ; i<timings.size() ; ++i) {
timings[i] = pow(timings[i]-avg, );
sum += timings[i];
}
double var = sum/(timings.size()-);
double sdv = sqrt(var); std::cout << " with fastest " << fastest << ", average " << avg << ", stddev " << sdv;
} double naive(const char *p) {
double r = 0.0;
bool neg = false;
if (*p == '-') {
neg = true;
++p;
}
while (*p >= '' && *p <= '') {
r = (r*10.0) + (*p - '');
++p;
}
if (*p == '.') {
double f = 0.0;
int n = ;
++p;
while (*p >= '' && *p <= '') {
f = (f*10.0) + (*p - '');
++p;
++n;
}
r += f / std::pow(10.0, n);
}
if (neg) {
r = -r;
}
return r;
} int main() {
std::vector<std::string> nums;
nums.reserve(N);
for (size_t i= ; i<N ; ++i) {
std::string y;
if (i & ) {
y += '-';
}
y += boost::lexical_cast<std::string>(i);
y += '.';
y += boost::lexical_cast<std::string>(i);
nums.push_back(y);
} {
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
for (size_t i= ; i<nums.size() ; ++i) {
double x = naive(nums[i].c_str());
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "naive: ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
} {
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
for (size_t i= ; i<nums.size() ; ++i) {
double x = atof(nums[i].c_str());
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "atof(): ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
} {
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
for (size_t i= ; i<nums.size() ; ++i) {
double x = strtod(nums[i].c_str(), );
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "strtod(): ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
} {
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
for (size_t i= ; i<nums.size() ; ++i) {
double x = 0.0;
sscanf(nums[i].c_str(), "%lf", &x);
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "sscanf(): ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
} {
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
for (size_t i= ; i<nums.size() ; ++i) {
double x = boost::lexical_cast<double>(nums[i]);
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "lexical_cast: ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
} {
using boost::spirit::qi::double_;
using boost::spirit::qi::parse;
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
for (size_t i= ; i<nums.size() ; ++i) {
double x = 0.0;
char const *str = nums[i].c_str();
parse(str, &str[nums[i].size()], double_, x);
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "spirit qi: ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
} {
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
for (size_t i= ; i<nums.size() ; ++i) {
std::istringstream ss(nums[i]);
double x = 0.0;
ss >> x;
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "stringstream: ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
} {
double tsum = 0.0;
std::vector<double> timings;
timings.reserve(R);
for (size_t r= ; r<R ; ++r) {
ticks start = getticks();
std::istringstream ss;
for (size_t i= ; i<nums.size() ; ++i) {
ss.str(nums[i]);
ss.clear();
double x = 0.0;
ss >> x;
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
} std::cout << "stringstream reused: ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
}
}