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 = 100000; static const size_t R = 7; void PrintStats(std::vector<double> timings) { double fastest = std::numeric_limits<double>::max(); std::cout << std::fixed << std::setprecision(2); std::cout << "["; for (size_t i = 1 ; i<timings.size()-1 ; ++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 = 1 ; i<timings.size() ; ++i) { sum += timings[i]; } double avg = sum / static_cast<double>(timings.size()-1); sum = 0.0; for (size_t i = 1 ; i<timings.size() ; ++i) { timings[i] = pow(timings[i]-avg, 2); sum += timings[i]; } double var = sum/(timings.size()-2); 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 >= '0' && *p <= '9') { r = (r*10.0) + (*p - '0'); ++p; } if (*p == '.') { double f = 0.0; int n = 0; ++p; while (*p >= '0' && *p <= '9') { f = (f*10.0) + (*p - '0'); ++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=0 ; i<N ; ++i) { std::string y; if (i & 1) { 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=0 ; r<R ; ++r) { ticks start = getticks(); for (size_t i=0 ; 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=0 ; r<R ; ++r) { ticks start = getticks(); for (size_t i=0 ; 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=0 ; r<R ; ++r) { ticks start = getticks(); for (size_t i=0 ; i<nums.size() ; ++i) { double x = strtod(nums[i].c_str(), 0); 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=0 ; r<R ; ++r) { ticks start = getticks(); for (size_t i=0 ; 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=0 ; r<R ; ++r) { ticks start = getticks(); for (size_t i=0 ; 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=0 ; r<R ; ++r) { ticks start = getticks(); for (size_t i=0 ; 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=0 ; r<R ; ++r) { ticks start = getticks(); for (size_t i=0 ; 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=0 ; r<R ; ++r) { ticks start = getticks(); std::istringstream ss; for (size_t i=0 ; 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; } }