|
||
This document summarises the techniques for improving application start-up time.
The techniques discussed are limited in scope to application programming. In other words, they do not require modification of any core OS components or servers. Some of the techniques are general good practice and are useful to developers at all levels.
The techniques can be summarised as follows:
Avoid causing unnecessary code to be executed as an effect of your code.
Defer construction of objects, loading of dynamic libraries (for example, the application model), and starting servers until you need to use them.
Write efficient code!
Note that there is an inevitable trade-off between shortening the start-up time and introducing latency elsewhere in the application. For example, if you don’t connect to a server at start-up, you will have to do it later on, when the application needs to use the server. This may be overcome by a central component that co-ordinates background connections, or construction, such as the view server.
Do not assume you know which methods take the most time. Pay close attention to iterative and recursive operations. Many useful tools and techniques are available to help identify poorly performing code:
Software analysis tools, for instance GlowCode and MetroWerks CodeTEST
The Symbian OS profiling tool, profiler.exe
, which is supplied on DevKits
The RDebug
class provides some profiling functions
The TTime
class can be used to time blocks of code, but beware of context switches, otherwise you may end up timing other threads!
Deliberately slowing down suspect blocks of code can reveal whether or not optimising it would bring a significant performance improvement.
Many applications instantiate their components, for instance error handlers, dialogs and menus, during start-up. In turn, each of these components may instantiate other components, for instance menu resources and icons. This can bring some benefits, for instance reducing application complexity, revealing memory allocation problems at start-up rather than after the application has been running for some time and improving the runtime performance of the application. However, to minimise application start-up time, it is recommended to avoid this behaviour. Your goal should be to only do what is immediately necessary during start-up.
Whenever recursion or iteration occurs, there is usually scope for significant speed improvements, especially in the most commonly used components. This is obvious but is often overlooked when looking for easy performance gains.
During application start-up, only construct UI components that appear in the application’s initial view. This applies especially
to the application's implementations of CXxxApplication::CreateDocumentL()
, CXxxDocument::ConstructL()
and CXxxDocument::CreateAppUiL()
, all of which are called before CXxxAppUi::ConstructL()
. Do not read bitmaps, resources, or any other data associated with the UI from files unless it is necessary.
In CXxxAppUi::ConstructL()
, make sure ActivateL()
and DrawNow()
are called on all controls that must be drawn when the application is launched. Also ensure that the client-side window server
command buffer is flushed by calling Flush()
on the application's window server session. This ensures that there aren't any drawing commands left in the client-side buffer,
after DrawNow()
has completed.
Often, when a large number of small images are required by an application, the overhead associated with loading each bitmap outweighs any benefit associated with their size. Some possible ways to avoid this are:
use text instead,
for very simple graphics, draw directly using drawing primitives rather than loading a bitmap,
concatenate many small bitmaps into one large bitmap file to reduce the need to search for and load multiple files.
Some GUI components redraw themselves every time their data changes. This may not always be necessary. Complicated GUI components
should implement their Draw()
method to only update the area of the screen that has changed. For example, there is no point in redrawing a whole list box
every time a new item is appended to it. In such cases, a GUI API should allow you to switch off redrawing. Beware of GUI
methods that cause the object they are called upon to redraw itself.
Use CCoeControl::DrawDeferred()
in preference to CCoeControl::DrawNow()
if possible, because excessive use of DrawNow()
can cause GUI flicker. For an explanation, see the documentation for CCoeControl::DrawDeferred()
.
Uncompressed ROM-based bitmaps that can be used in place from ROM are approximately three times faster to use than filestore bitmaps. Using them can bring a significant reduction in application start-up time.
Specifying bitmap=
instead of file=
in the .OBY
and .IBY
files when building the ROM causes bitmaps to be uncompressed before inclusion in the ROM. Other bitmaps need to be uncompressed
at runtime, which impacts performance.
The drawback of such bitmaps is that they are large (up to 3 times larger than file based) and cannot be compressed, although decompressing bitmaps should probably be avoided during start-up anyway due to the extra processing required.
If ROM space is limited, consider using such ROM-based bitmaps only if they are displayed during application start-up.
When bitmaps are drawn to the screen, optimum performance is achieved by ensuring that:
If this is the case, no palette mapping between the different colour depths is needed. On real hardware, this optimisation has been found to improve drawing speed by up to 10 times. However, in order to match the screen and window colour depth, bitmaps may need to increase in size and so this optimisation is only possible if the increase in ROM or RAM usage is acceptable.
The file server can be a major bottleneck during start-up when virtually all threads are searching for and loading data, libraries and plug-ins. Therefore reducing file access is one of the most effective ways to improve performance.
Resource files are used for localisation and allow modifications to be made to an application without the need to rebuild it, but they are expensive to use because they require access to the file system.
Many applications on a smartphone do not need to use documents, for example Telephony, Contacts (this uses the contacts database), Browser and Messaging.
By not specifying a default document filename, hundreds of milliseconds can potentially be saved from such applications' start-up time.
If an application uses a document file, application start-up may involve the following steps:
reading the name of the last used document file from the application’s .ini
file,
opening the document file, or if one doesn't exist, creating a default document file, after reading its name from the application's resource file,
writing the name of the last used file to the application’s .ini
file (which is created if it doesn’t exist),
writing an entry to the most recently used file list (mru.dat
),
additional document-related processing within CEikonEnv::ConstructAppFromCommandLineL()
.
The default document's name is read from the application's resource file by CEikAppUi::ProcessCommandParametersL()
. There are two ways of preventing the application from using a default document file:
Give the default document a NULL name in the resource file:
RESOURCE TBUF { buf=""; }
Override CEikAppUi::ProcessCommandParametersL()
to zero the document name and return EFalse
. This method is slightly more efficient because it avoids reading the default document name from the resource file altogether:
TBool CMyAppUi::ProcessCommandParametersL(TApaCommand /*aCommand*/, TFileName& aDocumentName, const TDesC8& /*aTail*/)
{
aDocumentName.Zero();
return EFalse;
}
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 OS 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 initialisation 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 behaviour.