MeeGo 1.2 Harmattan Developer Documentation Develop for the Nokia N9

QML performance tips and tricks

This section provides performance tips for Qt applications using QML. For more information on QML performance, see Qt documentation.

Best practices for QML application architecture

When programming with QML, consider the following best practices for application architecture:

  • Construct the QML UI without an extensive use of JavaScript.
  • Implement the application engines or application logic outside of the UI interface in C++ to improve efficiency.
  • Partition the application UI into QML files so that each file contains a logical UI entity. Do not use one large QML file for each application. This way, loading and unloading are easier to control.
  • Time-consuming operations (> N ms) must be implemented as asynchronous server calls, or by using threads so that the UI can update itself while the application is doing time-consuming back end operations.

Tips and tricks for showing QML in a Harmattan device

When programming with QML, consider the following when you want to show QML in a Harmattan device:

  • Qt Creator only supports deploying .pro file based project to a device.
  • You can convert a .qmlproject to a .pro file based project by creating a new Qt Quick Application Project:
  1. To start the wizard, click File -> New File or Project ...-> Qt Quick Project -> Qt Quick Application
  2. On the last page of the wizard, specify the root .qml file.

Best practices for QML development

When programming with QML, consider the following list of best practices:

  • Insert properties at the top of your element declaration.
  • Use Loader elements to segment memory use and implement lazy loading where applicable.
  • Use Qt's i18n concepts for internationalisation and localisation.
For example, you can use qsTr() to employ language-dependent resource loading for Image { source: qsTr("defaultFlagImage.png") }.
  • Create QServiceManager during idle time.
Creating a QServiceManager instance during application launch causes a 40-50ms delay.
  • Use images in their actual size.
Scaling is a heavy operation in QML.
  • For faster string operations, try QT_USE_FAST_CONCATENATION and QT_USE_FAST_OPERATOR_PLUS.
  • Wrap the state of the top-level QML item in an item.
Do not define states in the top-level item of reusable components because this exposes the top level state machine, which can cause breakage if additional states are defined when using the component. One workaround is to wrap the state of the top-level item in an item. This is especially important if it is likely that the component is used as a root object (for example, MyTopLevelWindow).
// example reusable component
Item {
   id: rootComponent
   // do not define states here
   Item {
      id: statesWrapper
      states: .... // define states here, this guarantees they will be internal
   }
}
Alternatively, you can use StageGroup, as it is lightweight compared to an Item. You can also have multiple state groups for independent states.
  • Use loaders.
The Loader item can be used to dynamically load and unload visual QML components defined in a QML file or items/components defined within a QML file. This dynamic behaviour allows the developer to control the memory usage and start-up speed of an application. Load the absolutely minimum amount of QML at start-up, do not create components unnecessarily. If your first view is very complex and requires plenty of QML to be parsed, show a splash screen or something similar. You can load pieces of UI only by demand, for example, when the user navigates to another view; on the other hand it may require more time to navigate between views.
  • Use threads or multiprocessing to allow responsive UI.
Use threads for the long lasting operations such as data retrieval to prevent the UI operations from blocking. In QML the WorkerScript element can be used to run JavaScript operations in a new thread.
  • In transition animations, make the animated area as small as possible.
If you need to move three elements in a second, try moving each at a time in 300ms. The system works so that it calculates the bounds of items that need repainting and paints everything inside those bounds. Animating two small objects in opposite corners can have adverse effects.
  • When integrating QML with a QGraphicsView-based UI, use QGraphicsView options for optimal performance.
  • Use cacheBuffer for ListView.
To achieve smooth scrolling, remember to define the cacheBuffer size for ListView.
  • Reduce the expense of Qt::SmoothTransformation without reducing image quality.
A combination of Qt::FastTransformation and half scaling helps to reduce effort for scaling images without losing the quality of the resulting image. The trick is to do the scaling in two steps:
  1. Scale down to two times of the destination size using Qt::FastTransformation
  2. Scale to the destination size by averaging two pixels in both dimensions using:
quint32 avg = (((c1 ^ c2) & 0xfefefefeUL) >> 1) + (c1 & c2);
Note: Previously we had suggested scaling down to a factor of 1.5 and applying smooth scaling. However this version should result in much better quality and it should be much faster. It should be faster as the new algorithm only requires bit arithmetic and bit shifting. So no floating point operations are needed anymore and there are no branches that require branch predictions (as would be the case for all kinds of bi-linear filtering).
This transformation shows approximately over five times faster scaling.
Drawback: This optimisation does not make any sense for smaller images. Therefore you need heuristic evaluation to decide whether it should be used.
Example:
// Smooth scaling is very expensive (size^2). Therefore we reduce the size
// to 1.5 of the destination size and using fast transformation afterwards.
// Therefore we speed up but do not loose quality..
if ( canvasPixmap.size().width() > ( 4 * size.toSize().width() ) ) // Primitive heuristic to decide whether optimization should be used.
    {
        // Improve scaling speed by add an intermediate fast transformation..
        QSize intermediate_size = QSize( size.toSize().width() * 2, size.toSize().height() * 2 );
        canvasPixmap = canvasPixmap.scaled( intermediate_size,
                                            aspectRatioMode,
                                            Qt::FastTransformation ); // Cheap operation!
    }
    canvasPixmap = canvasPixmap.scaled( size.toSize(),
                                        aspectRatioMode,
                                        Qt::SmoothTransformation ); // Expensive operation!

Conventions to avoid in QML development

When programming with QML, follow this advice to avoid less-than-optimal conventions:

  • Do not use JavaScript too much.
For example, do only small or trivial calls in handlers and for state changes in QML. Put business logic, and especially performance critical code either in WorkerScripts or in C++ code, or both. Use common sense.
Running a complex JavaScript expression for each frame of an x property animation has negative effects on performance. Instead, create a QDeclarativeExtensionPlugin and expose the needed QObjects to QML. In the QObject one exposes the properties, invokable methods, signals and slots needed in the UI. For any long (>2ms) operations one should use QRunnable with QThreadPool or QFuture.
If you end up doing a lot of JavaScript, you are doing something wrong.
  • Do not build too complex QML hierarchies both in terms of depth and structure.
QML code can be clean and elegant - using multiple layers of Items and Rectangles could incur a cost in the long term.
  • Do not instantiate many separate processes with QDeclarativeEngine.
Each QDeclarativeEngine has a JavaScript engine, which consumes a lot of RAM.
  • Be careful when importing other QML files to your application.
When a QML application starts, every import is parsed. This causes a delay to the application start-up time.
  • Do not mix too many levels of transparency, rather pre-render them as bitmaps (for example, PNG).
  • Do not abbreviate property names or element names, make your code readable and sensible.
  • Avoid creating large numbers of QDeclarativeView objects when integrating with a QWidget-based UI.
This may lead to performance degradation.
  • Avoid mixing when conditionals with setting states explicitly (state = "hidden").
Sometimes a Ternary operator, for example val1 == val2 ? cond1 : cond2, can get you very far.
  • Do not connect to QGraphicsScene::changed() to avoid rather expensive change area calculations.
  • Avoid unnecessary QRegExp construction.
QRegExp is faster at matching rather than construction. If you plan to do a lot of regular expression matching, construct your QRegExp once, for example as a static const variable, and use it many times.
  • Do not use qDebug unnecessarily.
qDebug() and related functions are expensive to call. They should only be used for diagnostic information of failures, not for tracking normal events.
  • Avoid duplicating containers by accident.
Qt's container classes such as QList, QStringList, QVector and QMap are all implicitly shared. That means that a simple copy from one to the other, for example as a function return value, does not cost much. Only a shallow copy is made, that is, only the container's management structure is really duplicated. The items are not copied, just a reference counter is incremented.
This is true as long as you do not try to modify the copy. And even then a deep copy (that is, all items are copied) is only made if the reference counter is greater than 1.
This is why the coding conventions insist that const_iterators should be used whenever possible: A non-const iterator is a write access to the container, causing a deep copy. begin() and end() on a non-const container are what get the deep copy started.
The code in the following example is harmless:
QDir dir(myPath);
QStringList files=dir.entryList();
for ( QStringList::iterator it=files.begin(); it != files.end(); ++files ) {
    myWidget->addItem(*it);
}
But this is just luck. The call to files.begin() would normally initiate copying the files QStringList that was returned by dir.entryList(). But since its reference count is back to 1 after returning from that function, there is no reason for a deep copy; files now hold the only reference to the items.
Be explicit. For example:
QDir dir(myPath);
QStringList files=dir.entryList();
for ( QStringList::const_iterator it=files.constBegin(); it != files.constEnd(); ++files ) {
    myWidget->addItem(*it);
}
  • Avoid implicit copies caused by operator[].
The code in the following example can be harmful:
QStringList nameList=myClass.nameList();
if ( nameList[0]==searchName ) { // deep copy of nameList
    ...
}
This seemingly harmless nameList[0] will cause a deep copy of nameList, duplicating each string it contains in the process. From this copy, element #0 is taken and compared with searchName. After the if() statement this temporary copy of nameList is immediately destroyed again. These operations are useless.
A temporary object is created because nameList[] is an LValue - a value that can be used on the left side of an assignment - for example:
nameList[0]=someValue;
This operator[] is a write access on its argument, and Qt's container classes initiate a deep copy on a write access.
It is possible that there is no adverse effect, though: QList and so on have overloaded versions of operator[]() for const containers. In that case, no deep copy will occur. The following example is safe:
void myFunc( const QStringList &nameList )
{
    if ( nameList[0]==searchedName) { // no deep copy: nameList is const
        ...
    }
}
But again, this is just luck. Be explicit and use at() instead of operator[] if you do not want to assign a value to that element.
Avoid a deep copy with code such as the following:
QStringList nameList=myClass.nameList();
if ( nameList.at(0)==searchName ) { // no deep copy
    ...
}
In the special case of at(0) it is even a little more efficient and considerably better readable to use first():
QStringList nameList=myClass.nameList();
if ( nameList.first()==searchName ) {
    ...
}
Prefer first() to at(0). Most Qt containers have first(). QString or QByteArray containers do not.