Code Efficiency

Attention paid to making code efficient in speed and particularly in resource use is always worthwhile. This topic suggests some methods that should become familiar to Symbian developers for this platform.

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;