Advertisement

C++ performance issues

Started by August 24, 2000 04:57 AM
2 comments, last by TheEarl 24 years, 4 months ago
I''m wondering what the overhead is off using classes likes structs, with the majority of methods inlined? For example, when loading a bitmap and creating a surface for it in directdraw, I''d like to be able to encapsulate the whole thing, so I create the class with a handle to a bitmap or a string with the location, and then do all the surface loading etc. Providing the getter methods are inlined, what kind of performance overhead will this kind of thing give? Thanks. "Careful with that Axe Eugene"
"We who cut mere stones must always be envisioning cathedrals"
It really depends on what you are doing. Using classes with non-virtual methods is roughly the same as simply passing a pointer to a struct to non-class functions. In code, this is what I mean with the struct:


struct mydata
{
int x;
int y;
};

void do_something(mydata* the_data)
{
}



And this is how it looks with classes:


class mydata
{
int x;
int y;

public:
void do_something();
};

void mydata::do_something() //note: no formal parameters!
{
}



Now, you can access the class data inside a class member function because of a little pointer called "this". Both of these statements are the same:


void mydata::do_something()
{
x = 5;
this->x = 5;
}



In fact, the compiler considers the first statement to be shorthand for the second. The "this" pointer is like a "hidden" parameter that is passed to your member functions, without your having to write it out each time. In other words, this is basically what the compiler does with the class we wrote:


class mydata
{
int x;
int y;

public:
void do_something(mydata* this);
};

void mydata::do_something(mydata* this)
{
}



The compiler just handles all that for you, and makes sure that the "this" pointer is filled in and everything. So, as you can see, it is little different from using data structures in C, at least from the performance point of view.

Inlining is a special keyword, which basically lets you write those nifty but troublesome C macros out as regular functions. Note that your program will be slower in debug builds because function inlining is disabled then in most compilers (that way you can step through the code). Let's look at how we use that struct again:


struct mydata
{
int x;
int y;
}

int main()
{
mydata the_data; // define an object

the_data.x = 5;
the_data.y = 7;
}



The compiler basically calculates the memory offset of each data member from the beginning of the struct, and the struct's address is assigned at run-time. Now let's look at a class approach:


class mydata
{
int x;
int y;

public:
inline const int get_x() const { return( x ); }
inline const int get_y() const { return( y ); }

inline void set_x(const int x) ( this->x = x; } // gotta tell it which x
inline void set_y(const int y) { this->y = y; } // gotta tell it which y
};

int main()
{
mydata the_data; // define an object

the_data.set_x(5);
the_data.set_y(7);
}



When the compiler does decide to inline those functions for you, it should all be optimized out to the same code as with the struct. All the temporary variables should be eliminated, and it should simply access the data in the same way. That's really the whole purpose of inline functions.

But you have to ask yourself, is the writing of all those inline functions really necessary? Do they actually protect the data? Take a look at this class:


class color
{
public:
unsigned char red;
unsigned char green;
unsigned char blue;
}; // a 24-bit color



I say that writing inline functions for that color class is unnecessary. Why? Because: 1) you haven't got any pointers or other tricky stuff (it's just a tiny four line class!), and 2) any value you can stuff into those bytes (0 to 255) is perfectly valid in any possible circumstance. So why bother writing those accessor functions?

Basically, you protect the data for classes that have to maintain what is called a "valid state," where certain combinations of data members could wreak havoc on the class and consequently any program that uses that class. Check out the following snippet:


class dynamic_array
{
public:
byte* array_of_bytes;
unsigned int size; // this is _not_ a place to skip accessors!

dynamic_array(unsigned int size)
{
array_of_bytes = new byte[size]; // allocate an array from the heap
this->size = size; // save the size
}

~dynamic_array()
{
delete[] array_of_bytes; // clean up
}
};

int main()
{
dynamic_array the_array(256); // create an array of 256 bytes

the_array.size = 512; // no!

// somewhere else, perhaps in another function:
for( unsigned int x = the_array.size; x < the_array.size; x++ )
{
the_array.array_of_bytes[x] = 0;
}
}



You can see that main() will either crash when x is greater than 256 (because we only allocated 256 bytes!), or it will simply corrupt memory. Neither is good. Part of the benefit of using classes is protecting yourself from mistakes like that. While this one was a bit obvious, many mistakes could occur in any of 10 possible modules, etc. depending on where you use that "the_array" object. We minimize those mistakes by allowing only the class member functions (typically stored in only 1 module) to change the object in a way that might make it crash.

Obviously, this is a Good Thing.

Happy Coding!


- null_pointer
Sabre Multimedia


Edited by - null_pointer on August 24, 2000 8:21:32 AM
Advertisement
Null pointer said it all. Don''t waste time encapsulating things that you don''t need encapsulating. Just as good design and forethought reveals a good way to organise your classes, it also shows where layers of abstraction etc are a needless burden. If you do that, then there''s no reason C++ should be any slower than C, when you really think through what is being called.
Well if all you are doing is creating and loading a bitmap then speed isnt a big issue. Since this is usually only done one time at the beginning of the game or in between levels, it doesnt interfere with your game loop.

This topic is closed to new replies.

Advertisement