Sunday, May 07, 2006

std::string + __FILE__ = malloc

So you start off like this:

#define CHECK_ERR(x) __CHECK_ERR(x,__FILE__,__LINE__)
void __CHECK_ERR(const char * msg, const char * file, int line)
{
if (g_error != 0)
printf("An error happened: %s (%s: %d.)\n", msg, file, line)
}

That’s a little goofy but we actually use something like this in X-Plane as a rapid debug-only way to spot OpenGL errors. The __FILE__ and __LINE__ macros give us pin-point messages about where the error was caught even on machines where we can’t easily attach a debugger.

Then later on you decide to embrace the STL string and do this:

void __CHECK_ERR(const string& msg, const string& file, int line);

Ouch. The danger (well, one of the dangers) of C++ is that it will change from a very low level to a very high level of abstraction, changing the underlying implementation from something fast to something slow, without ever telling you.

The problem is that despite using const string& for speed, your inputs (__FILE__ and __LINE__) are string literals. This code will thus create a new string object based on the const char * constructor every time this function is called, and clean up the object when done. In other words, __CHECK_ERR, which was a single if statement now allocates and deallocates memory. Ouch! Pepper error checks liberally around the tight loops that emit OpenGL calls and you’ll really see performance suffer.

This is a fundamental problem in picking between char * and std::string. If your API is declared as char *s but your internal implementation and client code is STL strings, the conversion means a slow allocation of memory where you could have had reference counting. But if your client code and implementation uses char *s and the interface uses STL strings, you allocate a string that isn’t needed.

Our solution with X-Plane is to know our client; virtually all routines use STL strings, except the cases where we know we’re going to fed by a string literal, like a __FILE__ macro. In those few cases we revert to const char *s and convert to STL strings at the latest possible moment to avoid the memory allocate.

No comments:

Post a Comment