This topic suggests some ways to improve speed and resource usage in Symbian C++ code.
Each thread in an application has a limited standard stack space of 8Kb, which should be carefully managed. Therefore:
avoid copy-by-value, except for basic types
create any large object or array on the heap rather than the stack
minimise the lifetime of automatic variables by appropriately scoping them
The last point can be illustrated with the following example:
void ABadFunction() { TBigObject Object1; TBigObject Object2; TBigObject Object3; GetTwoObjectValues(Object1,Object2); Object3=SumObjects(Object1,Object2); FunctionWithUnknownStackOverhead(Object3); }
In the above code, Object1
and Object2
persist, using stack space, throughout the lifetime
of the call to FunctionWithUnknownStackOverhead()
, although they are not required by that time. They should be removed
from the stack before the call is made. This can be achieved as follows:
void ABetterFunction() { TBigObject Object1; GetTotalObjectValues(Object1); FunctionWithUnknownStackOverhead(Object1); } void GetTotalObjectValues(TBigObject &aObject) { TBigObject Object1; TBigObject Object2; GetTwoObjectValues(Object1,Object2); aObject=SumObjects(Object1,Object2); }
By splitting the code into two functions, you ensure that the stack is used no more than required.
If a function definition has default arguments, and if that function often gets called with the caller assuming the default arguments, consider providing an overloaded function that doesn't have the additional arguments. This is because every time the compiler supplies a default parameter, it generates additional code where the function is called.
For example, if you have
void FunctionOne(TInt aInt=0);
which often gets called in code by the line
FunctionOne();
then consider supplying
void FunctionOne();
the contents of which might be:
void FunctionOne() { FunctionOne(0); }
Using a reference as a function argument may be more efficient than using a pointer. This is because the compiler has to preserve the value of the null pointer through all conversions.
Imagine a class CXxx
which derives from a mixin
class MYyy
, as in
class CXxx : public CBase,public MYyy {...};
Then, to pass a pointer to a CXxx
to a function
taking a MYyy
, the compiler has to add sizeof(CBase)
to the pointer, except when that pointer is NULL
. If cp
is a CXxx*
, and Func()
a function taking an MYyy*
, then
what happens in a call like Func(cp)
is something
like this:
Func((MYyy* aM)(cp==NULL ? NULL : (TUint8*)cp+sizeof(CBase)));
Null references are not possible, so no test for NULL
is necessary when they are used. On ARM, converting from CXxx*
to MYyy*
takes 8 instructions, whereas
the CXxx&
to MYyy&
conversion
takes only two.
Floating point maths is sufficiently slow that it is worth looking to see if an alternative algorithm using only integer maths is available.
For example, given two TInts
, aTop
, and aBottom
, instead of:
TReal a = (TReal)aTop; TReal b = (TReal)aBottom; TReal c = a/b+0.5; TReal result; Math::Round(result,c,0); return (TInt)result;
you should use
return((2*aTop+aBottom)/(2*aBottom));
Inline functions are intended to speed up code by avoiding the expense of a function call, but retain its modularity by disguising operations as functions. Before using them, however, there are two issues that you should check:
code compactness: limited memory resources may mean that the speed cost of a function call is preferable to large bodies of inline code
binary compatibility: changing the implementation of an inline function can break binary compatibility. This is important if your code is going to be used by other Symbian developers.
The most common cases where inline functions are acceptable are:
inline ConEnv() const { return iConEnv; };
inline TPoint::TPoint(TInt aX, TInt aY) { iX=aX; iY=aY; };
in the thin-template idiom: see Thin templates
certain other operators and functions, possibly templated, whose definition, not subject to change, is to map one operation onto another, for example,
template <class T> inline T Min(T aLeft,T aRight) { return(aLeft<aRight ? aLeft : aRight); }
C++ specifies
that delete 0
does nothing, so that you need never
write code such as
if (iX) delete iX;
This can be a cause of unnecessary file server use.
To prevent excessive drive access and scanning, always specify a drive letter in file paths, if known. The omission of a drive letter will cause all available drives to be searched in the standard Symbian platform order, in which Z: is always searched last.
Server requests involve context switching and may cause the server to run instead of the application. In the worse case if you make a request to a server that has not yet been started you may cause the server to start. This will involve creating a new thread (and possibly process) and running any server initialization code.
Synchronous operations or methods (particularly for server requests) can cause general application slowness, and in particular, a significant reduction in responsiveness. Synchronous requests to servers mean your thread is waiting, so that no start-up progress is being made.
No 'Golden Rule' exists about when to avoid synchronous requests. However, if an asynchronous version of a method exists, it is a good indication that the synchronous method could potentially take some time. Whilst it may take a little extra effort to handle asynchronous versions of method calls, you should consider very carefully any decision to use the synchronous version. It’s often easier to change from using an asynchronous version to synchronous than vice versa.
Note that in some situations, you might know that the server is implementing your asynchronous request synchronously. If this is the case, and the server runs with a higher priority than your application, then both versions of the API may have the same performance. However, using the synchronous version in this case has the drawback that it relies upon knowledge of the server's implementation, which could potentially change.
Opening a connection to a server is an expensive operation. If an application uses a server frequently then it should create one connection and leave it open until the application is destroyed. R classes declared as temporaries (on the stack, in other words) within a method may be a sign of this behavior.