[Scummvm-devel] Being memory & speed efficient when dealing with C++ objects
Max Horn
max at quendi.de
Sat Jun 3 18:21:13 CEST 2006
Hi there,
the following might be old news to some of you, but maybe a few
people out there can learn something from this, so I thought I post
it here anyway.
When dealing with objects in C++, one has to be careful if one wants
to be speed and memory efficient. C++ has this habit of copying
objects in places were you might not expect it. Especially if you are
from a Java / Python / other GC collected background.
Consider this statement:
String myStr = someOtherString;
This is in fact equivalent to
String myStr;
myStr = someOtherString;
In other words, it will first create the string object myStr (on the
stack). *Then* it will invoke the assignment operator, overriding the
default value of myStr. Not a big deal here: It means we allocate a
refcount pointer, only to delete it again a few cycles later. But
imagine we did this a couple hundred thousand times, and it adds up.
Or imagine we had used SomeBigObject instead of String, which has a
very expensive default constructor ... Not nice.
So the proper efficient way to write the above statement in C++ would
be:
String myStr(someOtherString);
This calls the copy constructor of class String directly, thus
avoiding the default initialization of the string class. Before you
shout now "boo, C++ is stupid, why doesn't it just 'Do What I
Mean' (TM) and optimize that useless default constructor call
away???". Simple: Because in many cases it can't, due to side
effects. Imagine we had a printf() in the default constructor. If the
compiler would simply optimize away the call to the default
constructor, it would also remove that printf call. Which would
result in changed program behavior. Which clearly is something an
optimizer is not allowed to do!
Another *very* typical pattern were people not very deeply intimate
with C++ code inefficiently are method parameters. Consider the
following three methods:
void MyClass::myClearVar1(String var) {
myVarTable[var] = 0;
}
void MyClass::myClearVar2(const String var) {
myVarTable[var] = 0;
}
void MyClass::myClearVar3(const String &var) {
myVarTable[var] = 0;
}
Look pretty similar, don't they? But they are quite different. If you do
myObj.myClearVar1(someVarName);
then this will actually create a temporary String object 'var' which
contains a *copy* of someVarName. The same holds for
myObj.myClearVar2(someVarName);
the only difference being that the copy is now declared const. Again,
the compiler can't just optimize this away, this time due to possible
side effects in the copy constructor.
The efficient way is to use variant 3, for
myObj.myClearVar3(someVarName);
will pass someVarName *by reference* (think of it like a pointer in
disguise). No copy is made.
Luckily, with our String class, the first two methods aren't that
bad, since our String class uses ref counting, so making copies is
cheap, as long as you don't modify the copied content (which is
impossible in myClearVar2, of course, since it is declared const).
Still, some memory on the stack will be used, and there is simply no
excuse not do use variant 3.
I hope this shed some light on this dark mystery. And please feel
free to ask should something be unclear!
Cheers,
Max
More information about the Scummvm-devel
mailing list