Code efficiency

This topic suggests some ways to improve speed and resource usage in Symbian C++ code.

Stack usage

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.

Function overloads

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);
    }

Pointers and references

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

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

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:

  • getter and setters for one- or two-machine word quantities: for example,

inline ConEnv() const { return iConEnv; };
  • trivial constructors for T classes:

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); }

No test for NULL pointer when deleting object

C++ specifies that delete 0 does nothing, so that you need never write code such as

if (iX)
    delete iX;

Drive scanning

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.

Only make server requests if you need to

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.

Use asynchronous server requests instead of synchronous server requests

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.

Do not repeatedly open and close connections to the same server

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.

Related concepts