C++ 28 May 2011 19:04:13
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 |
on 29 May 2011 at 06:04:09 1.metagoto said …
On my machine, Boost Spirit 2.4 with double_[ref(x) = _1] is 2 times slower than the naive loop. It competes with atof() and strtod()
on 29 May 2011 at 10:37:11 2.Tino Didriksen said …
I have added Boost.Spirit, but not sure if I used the correct parser; haven’t used it before. Correct me if there is an even faster way.
Still, it’s very fast…
on 29 May 2011 at 17:25:43 3.metagoto said …
@Tino Didriksen
Not sure if there is a faster way, but using Phoenix placeholders is Spirit’s spirit ;). We both have the exact same code.
on 29 May 2011 at 21:05:36 4.Christian said …
Where you using /Ox for VC++ ?
on 29 May 2011 at 21:23:53 5.Tino Didriksen said …
I was using /O2 /Ot, but changed to /Ox /Ot and updated the table. It’s a bit faster. Relatively, it’s the same though…these benchmarks aren’t about the absolutely fastest compiler or settings, but more about the overall faster method.
Full cmdline copied from the IDE is: /I”C:\Applications\Boost\boost_1_46_1″ /Zi /nologo /W4 /WX- /Ox /Ot /Oy- /GL /D “_UNICODE” /D “UNICODE” /Gm- /EHsc /MT /GS- /Gy /arch:SSE2 /fp:precise /Zc:wchar_t /Zc:forScope /openmp /Fp”Release\TestProject01.pch” /FAcs /Fa”Release\” /Fo”Release\” /Fd”Release\vc100.pdb” /Gd /analyze- /errorReport:queue … let me know if you can see any flaws.
on 30 May 2011 at 11:01:22 6.anon said …
That naive algorithm sounds great, where can i buy it?
You should paste it here too..
on 30 May 2011 at 11:11:16 7.Tino Didriksen said …
The full source code is available and linked in the post as http://tinodidriksen.com/uploads/code/cpp/speed-string-to-double.cpp
Mind you, I say it’s naive for a reason. I am sure it doesn’t conform to quality expectations that the other algorithms do. It’s just to have a neutral point of comparison.
on 30 May 2011 at 16:40:34 8.Hartmut Kaiser said …
Two minor comments:
a) You don’t need to utilize semantic actions with Spirit (and it is not true to say these are the spirit of Spirit). You can directly pass the variable to be filled:
parse(nums[i].begin(), nums[i].end(), double_, x);
This saves (at least) one double assignment.
b) The current code lets Spirit parse from the string iterators, while most of the other codes parse from the plain character buffer. If you do the same for Spirit you’ll see further speedup:
char const* str = nums[i].c_str();
parse(str, &str[nums[i].size()], double_, x);
Thanks for doing this analysis!
Regards Hartmut
on 30 May 2011 at 17:26:40 9.Tino Didriksen said …
Alrighty, updated to use the latter way.
Compared to the old code, that made the Spirit method 3% faster for VC++, 5% faster for g++, and no change for clang++.
on 02 Aug 2011 at 12:11:49 10.Piotr said …
Try this one (not mine, but works):
http://www.leapsecond.com/tools/fast_atof.c
It’s a replacement for atof() – much faster.
on 20 Feb 2013 at 00:35:07 11.Arash Partow said …
Have you considered including StrTk in your listings?
The tests used in that library for string to double/float conversion are a little more extensive.
At the end of the article a number of comparisons between, stdlib, Boost, Spirit and StrTk can be found using various hardware configurations/compilers etc.
http://www.codeproject.com/KB/recipes/Tokenizer.aspx
on 28 Apr 2014 at 16:15:45 12.Duke said …
Nice list.
@stringstream: when you only measure the timings of serializing the values (and not the storing the values in the stringstream container) the timings are identical with atof and strtod.
e.g.
{
double tsum = 0.0;
std::vector timings;
timings.reserve(R);
std::stringstream* ss = new std::stringstream[N];
for (size_t r=0 ; r<R ; ++r) {
for (size_t i=0 ; i<nums.size() ; ++i) {ss[i].str(nums[i]); }
ticks start = getticks();
for (size_t i=0 ; i> x;
tsum += x;
}
ticks end = getticks();
double timed = elapsed(end, start);
timings.push_back(timed);
}
delete[] ss;
std::cout << "stringstream without init: ";
PrintStats(timings);
std::cout << std::endl;
std::cout << tsum << std::endl;
}
on 20 Jul 2014 at 15:45:42 13.Gabriel said …
Here is the fastest generalized Method (float,double) with valid conversion tests!
It is as fast as the fastest of your code! Testet with gcc 4.7 linux:
#define white_space(c) ((c) == ‘ ‘ || (c) == ‘\t’)
#define valid_digit(c) ((c) >= ‘0’ && (c) <= '9')
template
bool naive(T & r, const char *p) {
// Skip leading white space, if any.
while (white_space(*p) ) {
p += 1;
}
r = 0.0;
int c = 0; // counter to check how many numbers we got!
// Get the sign!
bool neg = false;
if (*p == ‘-‘) {
neg = true;
++p;
}else if(*p == ‘+’){
neg = false;
++p;
}
// Get the digits before decimal point
while (valid_digit(*p)) {
r = (r*10.0) + (*p – ‘0’);
++p; ++c;
}
// Get the digits after decimal point
if (*p == ‘.’) {
T f = 0.0;
T scale = 1.0;
++p;
while (*p >= ‘0’ && *p std::numeric_limits::max_exponent10 ){
e = std::numeric_limits::max_exponent10;
}else if(e < std::numeric_limits::min_exponent10 ){
e = std::numeric_limits::max_exponent10;
}
// SECOND CHECK:
if(c==0){return false;} // we got no exponent! this was not intended!!
T scaleE = 1.0;
// Calculate scaling factor.
while (e >= 50) { scaleE *= 1E50; e -= 50; }
//while (e >= 8) { scaleE *= 1E8; e -= 8; }
while (e > 0) { scaleE *= 10.0; e -= 1; }
if (negE){
r /= scaleE;
}else{
r *= scaleE;
}
}
// POST CHECK:
// skip post whitespaces
while( white_space(*p) ){
++p;
}
if(*p != ”){return false;} // if next character is not the terminating character
// Apply sign to number
if(neg){ r = -r;}
return true;
}
on 20 Jul 2014 at 15:47:04 14.Gabriel said …
here the link to the code:
http://pastebin.com/dHP1pgQ4
on 31 Mar 2015 at 12:06:15 15.me said …
Your naive doesn’t read the exponent, as in “0.1E+01”.
on 31 Mar 2015 at 14:16:06 16.Tino Didriksen said …
Regarding exponents: Hence why I wrote “Boost.Spirit is the fastest correct implementation”, and I previously commented it wasn’t meant to be a conforming comparison function.
on 17 Jul 2018 at 12:48:25 17.Jon said …
Did anyone test the speed of the parsing routines of https://github.com/google/double-conversion/ (most famous for its printing routines using grisu3 from Florian Loitsch, 2010) ?
on 13 Dec 2021 at 18:24:14 18.Jon said …
I know this is old but may as well say it:
If you factor out that pow() the naive function in the linked code runs about twice as fast.
change ‘n’ to a double starting at 1.0
then multiply by 10 each loop
and divide by n.
that changed my ~0.05 sec to ~0.025 for 1 million random text-doubles timed with chrono.
on 08 Feb 2023 at 06:43:54 19.Plinio Andrade said …
Hi Tino,
It would be very nice for you to redo this investigation using more up to date compilers.
I am curious to see the conversion to float also.
Great job and very well presented.