Rants on software, computing, and any other topic I feel like.

Wednesday, November 24, 2010

Exceptions are Faster

There's a lot of C++ hating around these days. This hate doesn't always come from the world of young bucks in love with Python. Seasoned C programmers like to hate on C++ for it's nice features. Because, as all good purtitans know, anything that feels good or makes life easier must be bad.

One particular C++ language feature that is generally avoided are exceptions. Even C++ programmers, who love exceptions for their ability to clean up ugly error handling code, are guilty of this. The general consensus seems to be that exceptions should only be used in parts of code that aren't time sensitive. Cache invalidation and all sorts of other badness are usually the reasons given that exceptions are slow.

The alternative in that situation is to use return codes. Yeah, sure they're ugly and use up that nice return value slot that could otherwise be used for something nice, but nothing is sacred when performance is required.

Well, I was thinking about that today and decided that this is all wrong.

The logic is this: In order to handle return codes properly, you must check them all the time. When you're writing time sensitive code, checking an error code that isn't going to be set 99.9% of the time is a big fast waste of time. In fact, looking at any flag that isn't set 99.9% of the time is really stupid. There's usually a better way to handle the logic. Spending the same amount of time for rare cases and common cases doesn't make sense. It's okay for the code that handles rare cases to be slow as long as the common case is as fast as possible. The overall speed won't be significantly affected by the rare case.

This is why exceptions are so great. With exceptions, you don't actually check anything most of the time. In other words, the common case of no error, takes no time at all. The compiler takes care of figuring out where exceptions should be caught at the points in the code where they're thrown. The way it does that is to simply "goto" the catch point. When using an error code, the calling code is checking for the error code every time. Yeah, it's slow because it's jumping all over the place. But so what? It only happens on rare occasions, which can be slow. If your code is designed such that errors are relatively rare occurances (as it should) then there is likely very little impact. Of course, if you're using exceptions for program logic, then well, you're doing it wrong.

All right, here's the proof that exceptions are faster than return codes. I created simple class that increments a value until it hits some maximum value. It is considered an error when it hits that maximum value. The return value version of this simply returns false when an error occurs, while the exception version throws an exception.

The return value version:

class inc
{
public:
inc(int m) : m_max(m), m_count(0) {}
bool go(int& x)
{
if (m_count > m_max)
return false;
x = ++m_count;
return true;
}
private:
int m_count, m_max;
};

int main()
{
int count = 1000000000;
inc f(count);
int x;
while (1)
{
if (!f.go(x))
{
printf("error\n");
return 1;
}
}
}

The exception version:

struct my_exception : std::exception
{
virtual const char* what() const throw () { return "error"; }
};

class inc
{
public:
inc(int m) : m_max(m), m_count(0) {}
int go()
{
if (m_count > m_max)
throw my_exception();
return ++m_count;
}
private:
int m_count, m_max;
};

int main(int argc,char** argv)
{
int count = 1000000000;
try
{
inc f(count);
int x;
while (1)
x = f.go();
}
catch (std::exception& e)
{
printf("%s\n", e.what());
return 1;
}
}

Before we cut to what you're waiting for, the benchmark, let's look at a couple things here.

First, the exception version is more self-documenting. When the error occurs, it's clear what the error is. It also catches more errors than just the one I'm aware of. Any other standard exceptions will be caught and the program will then exit gracefully with a possibly helpful error message. To accomplish this with the return code version, we would have to setup a error code system with associated enums, functions, etc. With this system, we just hook into the already created std::exception class.

Second, the code for the exception version is more elegant. Take the signture for the "go" function for example. In the return value version, I'm already using the return value for an error code, so I have to return the value by parameter reference, which isn't the natural way you'd want to do this. The exception version does the natural thing and just returns the value.

Now, the benchmark. I didn't do lots of testing with lots of compilers and so forth. Feel free to do so and torture me mercilessly, but I have my doubts that you'll get widely different results. On my machine here at work, an i7, the above code runs in about 4.8 seconds when using return values and 3.6 seconds when using exceptions, a 33% improvement.

Labels: ,

This page is powered by Blogger. Isn't yours?