C++ performance issues
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:
And this is how it looks with classes:
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:
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:
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:
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:
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:
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:
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
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
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.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement