Geeks With Blogs
Timo Heinäpurola

A few years back I bumped into a rather peculiar optimization case. I did some performance analysis on mesh loading because at the time it took about 15 seconds to load a relatively small ANSI encoded mesh file. 15 seconds! I tracked the reason down to a call to atof, which is what I would use for parsing floating point values from strings.

Having found out what the reason was I decided to write my own implementation of floating point parsing that would concentrate on the essentials like speed instead of doing all kinds of culture checks and what not. Having done this I found out it was a very good idea indeed. Loading times dropped from 15 seconds to much under a second! If my memory doesn’t deceive me the speedup factor was somewhere around 100.

I recently tested whether the issue with atof was still there and it turns out it’s not that bad but it’s there all right. Testing with 1 000 000 iterations it took about 1.318 seconds from atof and 0.1016 seconds from my custom implementation.

Here’s the implementation of the function. It could still be optimized further, of course, and the precision is not as good as with atof, but it’s good enough.

RRFloat_t ParseFloat( const RRChar_t *str )
{
    RRUInt_t offset = 0;

    while ( str[offset] != '\0' &&
            str[offset] != '-' &&
            ( str[offset] < '0' ||
                str[offset] > '9' ) ) {
        offset++;
        if ( str[offset] == '\0' ) {
            return 0.0f;
        }
    }

    RRFloat_t val = 0.0f;
    RRBool_t neg = false;
    if ( str[offset] == '-' )
    {
        offset++;
        neg = true;
    }
    RRFloat_t curNom = 1.0f;
    while( str[offset] != '.' )
    {
        val = val + (RRFloat_t)( str[offset] - '0' );
        offset++;

        switch( str[offset] )
        {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '.':
            break;
        default:
            if ( neg )
                return -val;
            return val;
        }

        if ( str[offset] != '.' )
            val *= 10.0f;
    }
    offset++;
    curNom = 0.1f;
    while( str[offset] >= '0' && str[offset] <= '9' )
    {
        val = val + curNom * (RRFloat_t)( str[offset] - '0' );
        curNom *= 0.1f;
        offset++;
    }

    if ( neg )
        return -val;
    return val;
}

Simple enough. What’s the purpose of this blog post, you might think? When need be, don’t be afraid to get your hands dirty and just do it yourself!

Posted on Sunday, August 21, 2011 3:42 PM | Back to top


Comments on this post: When it makes sense to reinvent the wheel

# re: When it makes sense to reinvent the wheel
Requesting Gravatar...
There are actually several reasons for atof performance. One is the complicated code that parses the number in more than double (s53e10) precision to get accurate results - I doubt it's possible to write a precise atof that returns double; another one is that it does a strlen on the input string, so if you're using it on a large space-delimited string several times, you'll essentially get a quadratic behaviour.
Left by Arseny Kapoulkine on Aug 22, 2011 8:50 PM

# re: When it makes sense to reinvent the wheel
Requesting Gravatar...
Good point. And that's also why atof is not the best option for parsing files that contain geometry. Typically you don't need all the precision and your format is often specifically formatted.
Left by Timo Heinäpurola on Aug 23, 2011 8:33 AM

Your comment:
 (will show your gravatar)


Copyright © raccoon_tim | Powered by: GeeksWithBlogs.net