SatView: Pointer Perfect, part 4 - Performance Hit When Initializng
(Page 4 of 5 )
Something you might easily overlook is that declaring an array of objects invokes the constructor for each object in that array. When this object takes some time to create, declaring an array of these objects might claim a hit on the performance of your application.
When you are not sure whether you will need all 1000 objects in the array, it might be wiser to reserve 1000 pointers instead of the objects themselves. Of course don’t forget to initialize all the pointers to NULL and to create/delete them as you need.
Programmers aware of this behavior are sometimes still caught by surprise when using the std::vector. The vector is very handy to use instead of built-in arrays, because it can be treated like a built-in array (this is in fact one of its requirements) and can grow dynamically as well. Don’t let this mislead you into thinking that you will only be creating as many objects as you want to put into it when you are not taking the necessary precautions.
It is in fact cheaper to construct a built-in array of 10 objects than to use a plain std::vector when you will only need 5 of the objects! Let me demonstrate.
class MyClass {
public:
MyClass() { (void)printf(“>> MyClass constructed <<\n”); }
MyClass(MyClass const &other)
{ (void)printf(“** MyClass copy constructed **”); }
MyClass& operator=(MyClass const &other)
{ (void)printf(“** MyClass re-assigned **\n”); return *this; }
~MyClass() { (void)printf(“>> MyClass destructed <<\n”); }
};
void test()
{
MyClass myClass;
(void)printf(“creating an array[5].\n”);
MyClass arry[5];
int idx;
for (idx=0; idx<5; ++idx)
{
(void)printf(“add #%d.\n”, idx);
arry[idx] = myClass;
}
(void)printf(“done.\n\n”);
(void)printf(“creating a plain vector.\n”);
std::vector<MyClass> vect;
for (idx=0; idx<5; ++idx)
{
(void)printf(“add #%d.\n”, idx);
vect.push_back(myClass);
}
(void)printf(“DONE.\n\n”);
(void)printf(“creating a vector… ”);
std::vector<MyClass> vect2;
(void)printf(“and reserving space for 5.\n”);
vect2.reserve(5);
for (idx=0; idx<5; ++idx)
{
(void)printf(“add #%d.\n”, idx);
vect2.push_back(myClass);
}
(void)printf(“DONE.\n\n”);
}
Let's take a look at what the first part, creating an array[5], generates for output:
creating an array[5].
>> MyClass constructed <<
>> MyClass constructed <<
>> MyClass constructed <<
>> MyClass constructed <<
>> MyClass constructed <<
add #0.
** MyClass re-assigned **
add #1.
** MyClass re-assigned **
add #2.
** MyClass re-assigned **
add #3.
** MyClass re-assigned **
add #4.
** MyClass re-assigned **
DONE
Nothing surprising here…exactly what we expected: MyClass was constructed 5 times, just by declaring an array[5] of it. Then myClass was copied into 5 objects in that array by using the assignment operator. Now take a look at the output of the second part; the plain vector getting MyClass pushed into it 5 times:
creating a plain vector.
>> MyClass constructed <<
add #0.
** MyClass copy constructed **
** MyClass copy constructed **
>> MyClass destructed <<
add #1.
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
>> MyClass destructed <<
>> MyClass destructed <<
add #2.
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
>> MyClass destructed <<
>> MyClass destructed <<
>> MyClass destructed <<
add #3.
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
>> MyClass destructed <<
>> MyClass destructed <<
>> MyClass destructed <<
>> MyClass destructed <<
add #4.
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
** MyClass copy constructed **
>> MyClass destructed <<
>> MyClass destructed <<
>> MyClass destructed <<
>> MyClass destructed <<
>> MyClass destructed <<
DONE.
What is going on here you might wonder? It is quite easy to explain: std::vector reallocates enough memory to hold its current number of objects plus one extra, every time we push another myClass into it. The first “copy constructed” message and the last “MyClass destructed” message are for a temporary object holding a copy of myClass.
The other messages are for copy constructing the current objects into the reallocated memory plus one for the object we are pushing into the vector. The destruction messages are for the objects being freed from the current allocated memory since they are not needed anymore.
It is pretty clear that using a built-in array of 10 objects is much cheaper than the way we are using the vector in the sample above! The third part shows proper usage of the std::vector.
creating a vector... and reserving space for 5.
add #0.
** MyClass copy constructed **
add #1.
** MyClass copy constructed **
add #2.
** MyClass copy constructed **
add #3.
** MyClass copy constructed **
add #4.
** MyClass copy constructed **
DONE.
This is exactly the type of behavior we want to see from the std::vector! It goes without saying that you need to understand what you are doing and how libraries (like the STL) behave when you are using them.
Always measure your code before assuming where performance bottlenecks might reside. In the case of large arrays of expensive objects being the problem, resolving to pointers might solve your problem.
Next: Object Slicing >>
More C# Articles
More By J. Nakamura