App performance

Want to get the best possible performance out of your Cascades application? The first thing to do is make sure that your application complies with the following performance check list.

While there are many things that you can do to increase the performance of your application, these are the major guidelines that you should try to follow if you need to improve your app's performance.

Compile resources

Compiling application resources is a way that you can decrease your app's loading times without making changes to your code. In an application that doesn't compile resources, QML files are stored within the app's assets folder in the file system. Accessing these QML files can be very inefficient for applications that contain a lot of QML. To help speed things up, you can compile your QML files as a resource that's built in to your application's binary.

For more information about compiling QML, see Compile resources for Cascades apps.

Use QML efficiently

If your application is experiencing slow start times or sluggish response times when rendering the UI, there might be some optimizations that you can make to load QML more efficiently.

The most straightforward way of creating a UI in Cascades is to define one large, hierarchical structure in QML. Unfortunately, this approach can have a negative effect on start time and memory consumption. In addition, having too many controls linked to the scene graph at the same time can affect the overall UI rendering performance. A component is linked to the scene graph once it's added to the node (or a sub-node) that is currently set as the root of the application's scene (set by Application::instance()->setScene()). To avoid these problems, there are a few strategies that you should consider when you're developing your UI:

Keep attachedObjects simple

If you are using attachedObjects to expose C++ objects to QML, don't run time-consuming processes in their constructors (for example, SQL queries, blocking calls to services, and so on) because these processes are run when the QML is loaded.

Instead of running these processes in the constructor, create a Q_INVOKABLE function containing those processes that you can call from QML. See Using C++ classes in QML for more details on exposing C++ classes to QML.

Use deferred loading

At any given time, only the minimum amount of required QML should be loaded into memory. When your app starts, only load what the user sees first. After start-up, asynchronously load the content that is required for the next possible interactions, and continue this pattern for each subsequent interaction. When components are no longer needed, destroy them. For example, if your application contains a TabbedPane or a NavigationPane, you should load only the QML that's required for the first page, with empty stubs for the subsequent pages. After the first page loads, you can load additional pages asynchronously.

Another approach is to load content on demand. This approach works well if you're not loading extremely large amounts of content. If possible, try to load components to prepare for a possible interaction so that the user isn't left waiting for it.

One way to handle deferred loading in Cascades is to use the ComponentDefinition and ControlDelegate classes. ComponentDefinition represents an imperative method of creating dynamic QML components, while ControlDelegate is the declarative method. For more information about ControlDelegate and ComponentDefinition, see Dynamic QML components.

Using the visible property on controls isn't an efficient way to handle deferred loading. Even though the visible property for a control might be set to false, the control is still loaded into memory, affecting both start time and page creation time. Generally, the visible property should only be used when controls are temporarily hidden. If there's a good chance that a control will not be shown at all, it's better to defer its creation altogether.

Create similar items using ComponentDefinition

If you have multiple items that are very similar to create, you should create them dynamically from a ComponentDefinition instead of reusing the QML code throughout the file. In this example, a Container is created multiple times and added to the scene. The index property is used to identify the different versions of the container and apply the appropriate background color.

import bb.cascades 1.0

Page {
    Container {
        id: rootContainer
        attachedObjects: [    
            ComponentDefinition {
                id: component
                
                // Simple container without a background color
                Container {
                    property int index;
                    preferredWidth: 100    
                    preferredHeight: 100
                    
                    // Colors the background based on the index
                    background: switch (index) {
                        case 0: Color.Black; break;           
                        case 1: Color.DarkGray; break;          
                        case 2: Color.Gray; break;           
                        case 3: Color.LightGray; break;       
                    } 
                }    
            }    
        ]
    }
    onCreationCompleted: {
        for (var i = 0; i < 4; i++) {  
            // Creates the container and sets its index 
            var item = component.createObject();            
            item.index = i;            
            rootContainer.add(item);   
        }
    }
}

Reuse text styles when possible

Although text style settings are automatically cached by the application, you should still try to reuse text styles when possible. Creating multiple TextStyleDefinition objects in QML can increase load time, though it's still sometimes necessary for the maintainability of your code.

Do this:

Container {
    attachedObjects: [
        TextStyleDefinition {
            id: myStyle
            base: SystemDefaults.TextStyles.BigText
        }
    ]
    Label {
        text: "This is a label."  
        textStyle {
            base: myStyle.style
            color: Color.Red
        }
    }     
    Label {
        text: "This is another label."          
        textStyle {
            base: myStyle.style
            color: Color.Black
        }
    }       
}

Avoid doing this:

Container {   
    Label {
        text: "This is a label."  
        textStyle {
            base: SystemDefaults.TextStyles.BigText
            color: Color.Red
        }
    }     
    Label {
        text: "This is another label."          
        textStyle {
            base: SystemDefaults.TextStyles.BigText
            color: Color.Black
        }
    }
}

For more information about text styles, see Text styles.

Avoid declaring variant properties

The variant type in QML is quite useful since it allows you to store values from any of the basic Qt types. Although it's versatile, the versatility comes with a price. Using variant when you can use a more specific type means that you get less compile-time help (the compiler might not recognize certain type-related errors) and you pay a performance penalty from converting the value in to and from a QVariant each time. If the variable is always a number, you should use the int or real types. If the variable is always text, use a string.

Do this:

property int aNumber : 100
property bool aBool : true
property string aString : "Hello!"

Avoid doing this:

property variant aNumber : 100
property variant aBool : true
property variant aString : "Hello!"

The variant type can also be used to store more complex objects, such as controls, or any other class type that is exposed to QML. Here's an example of how to declare a property of the Container type. When you initialize the object, you must use null instead of 0, since 0 represents a literal integer.

property Container myContainer : null

However, there are still some instances when you must use the variant type to store object references. For example, if your application passes the object reference within a signal handler you still must use a variant to store the object reference.

Avoid using JavaScript files to store properties

Using JavaScript files to store properties is inefficient and unsafe. When you import a JavaScript file into QML, property types are lost, so you must reassign your properties in QML before you can use them. Changing the value of a JavaScript property can also cause issues. When you load a JavaScript file into QML, a new object is created for each QML file that you import the JavaScript file into. If you change any of the JavaScript property values, your data might become out of sync across your app.

To create safer and more efficient code, you should define JavaScript properties in QML instead.

Do this:

// Constants.qml

// QtObject is the most basic non-visual type
QtObject { 
    property int a: 123;
    property real b: 4.5;
    property string c: "6";
}
// QML file where the Constants.qml properties are used

import bb.cascades 1.0
import "../common"

Container {
    attachedObjects: [ 
        Constants { 
            id: constants 
        }
    ]
    property int a: constants.a
    property real b: constants.b
    property string c: constants.c
}

Avoid doing this:

// Constants.js

var a = 123;
var b = 4.5;
var c = "6";
// QML file where the Constants.js properties are used

import bb.cascades 1.0
import "../common/Constants.js" as Constants

// Reassigning the property types
Container {
    property int a: Constants.a
    property real b: Constants.b
    property string c: Constants.c
}

Don't block the UI thread

Does your overall application performance seem sluggish or unresponsive? The poor performance might be due to running too many resource-intensive processes on the UI thread. Here are a few things that you should consider when loading data or running other resource intensive operations:

Load data asynchronously

You should always try to avoid loading data in response to a user's request for it. If possible, large amounts of data should be preloaded to prepare for a request. Loading data asynchronously is especially important when using a ListView. For small sets of data, loading data preemptively might not be as important, but when handling large sets of data, you need a DataModel that is able to continually provide data to a scrolling ListView. For more information about lists and asynchronous data providing, see Asynchronous data providing.

Run resource-intensive processes on a separate thread

Many resource-intensive processes in Qt are asynchronous in nature and don't require the creation of separate threads. For example, many of the QtNetwork APIs automatically process networking requests on a separate thread. When you do have resource-intensive processes that are affecting UI performance, you can use QThread to run these processes on a separate thread. And if you don't want to set up a separate thread, you can use QtConcurrent::run to run functions asynchronously. For more information about QThread, see Thread support.

Start the event loop right away

For apps that request a large amount of data over the network or perform heavy operations when they start, you should always start the event loop before these tasks are initiated. If you don't start the event loop by calling Application::exec() the user might experience a noticeable lag between starting the app and seeing the UI.

Here's an example of how to start the event loop before you initiate any other operations.

Not applicable

// Declare a class with an invokable init() method
class MyClass : public QObject {
    public:
    MyClass();
    ...
    private:
    Q_INVOKABLE void init();
}
// Create an instance of the class in main
int main(int argc, char **argv)
{
    Application app(argc, argv);
    MyClass c;
    return Application::exec();
}
// In the constructor of the instantiated class, invoke init() and
// do nothing else.
MyClass::MyClass() : QObject()
{
    QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
    // The Qt::QueuedConnection param specifies that the event
    // loop must start before init() can be executed.
}
// Invoked after the event loop starts
MyClass::init()
{
    // Time consuming operation such as a network request or
    // loading a really large UI
}

Starting the event loop immediately is even more important with headless apps. If the event loop for a headless operation doesn't start within 6 seconds from when the app starts, the OS automatically terminates the app.

Optimize your lists

Although they're very powerful and flexible, lists can also be very intensive on resources if used incorrectly. There are a number of things you can do to ensure that you're getting the best possible performance out of your lists.

Define multiple ListItemComponent objects

If your ListView features different types of list items, always create a specialized ListItemComponent for each variation. You should avoid using a single ListItemComponent and changing the visibility of the content depending on the data. The ListView creates every node in a list item, regardless of whether they're visible.

Keep list items simple

In general, it's best to keep list items simple. Extraneous controls within a ListItemComponent can slow down performance. For very large list items, consider using Dynamic QML components.

Load images asynchronously

In a list, always load images asynchronously so that the list can scroll while images are still loading. To load an image asynchronously, you must use the file:/// prefix with the absolute path to the image.

For more information about loading images asynchronously, see Images.

Use assets and colors efficiently

There are several best practices that you should follow to ensure that your application uses assets efficiently.

Avoid using duplicate images

Using duplicate image assets in your application might seem harmless, but it can have a negative impact on performance, memory, and disk space. After an image is loaded into memory, Cascades stores it in a texture cache. When the same image is loaded again, it loads faster and doesn't take up any extra memory. However, if you have a duplicate image in your assets folder, it's treated as a new image and has to be created and stored in memory again.

Unused images don't affect memory consumption at all, but they do affect disk space.

Nine-slice scale your images

Nine-slice scaling is typically used for background and border images since it allows an image to scale to virtually any size while keeping its corners sharp and borders uniform. Using nine-slice scaling can help minimize the size and number of image assets that you bundle with your application. For more information about nine-slice scaling, see  Nine-slice scaling.

Remove transparent pixels

If your images contain transparent pixels to provide padding, trim the transparent pixels and create the space in your app using padding or margins instead. Trimming the transparent pixels can reduce the size of the image.

Do this:

Screen showing an image with no padding.
ImageView {
    imageSource: "asset:///tatLogo.png"
    leftMargin: 10
    rightMargin: 10
    topMargin: 10
    bottomMargin: 10
}

Avoid doing this:

Screen showing an image with padding.
ImageView {
    imageSource: "asset:///tatLogo.png"
}

Use tiling

For backgrounds, use the tiling functionality in ImagePaintDefinition to repeat textures within a Container.

Do this:

Checkerboard tile
// checkerboard_40x40.amd

#RimCascadesAssetMetaData version=1.0
source: "checkerboard_40x40.png"
repeatable: true
Container {
    preferredWidth: 200
    preferredHeight: 100
    background: tile.imagePaint
    
    attachedObjects: [
        ImagePaintDefinition {
            id: tile
            repeatPattern: RepeatPattern.XY
            imageSource: "asset:///images/checkerboard_40x40.amd"    
        }    
    ]
}

Avoid doing this:

Full checkerboard
ImageView {
    imageSource: "checkerboard_200x100.png"    
}

Use images of the correct size

Always make sure that you're using images that are the correct size for your application. If you have an image that is 100 px wide and you want to render it at 50 px, scale the actual image instead of scaling it in the application.

In addition, consider using a 3rd party application to reduce the size of PNG images that you use in your application. Smaller file sizes result in quicker loading times and use less disk space. Even if your compressed PNG is only a couple of kilobytes, it will take up at least (width x height x 4) bytes of graphics memory. Gradients are usually very well compressed in PNG format but still use the same amount of graphics memory.

Replace images with colors

Are you using any single-color images in your application? If possible, you should replace these with containers that have their background colors set.

Do this:

Container {
    background: Color.Red
}

Avoid doing this:

Container {
    ImageView {
        imageSource: "asset:///bigRedImage.png"
    }
}

Reuse colors

Every time the Color.create() function is called, new memory for the color is allocated. The simple solution for this problem is to use the predefined color constants whenever possible.

Do this:

Container {
     background: Color.White
}

Avoid doing this:

Container {
     background: Color.create("#ffffff") 
}

This is a very efficient solution, except that there isn't a predefined constant for every possible color. In these cases, you can define the colors as properties and reference them multiple times.

property variant myColor : Color.create("#8072ff");

Container {
    background: myColor
}

But what about when you need to reference the same color in multiple QML files? You can define the color in C++ and expose it to all your QML files.

Declare the color:

Q_PROPERTY(bb::cascades::Color niceColor READ niceColor CONSTANT)

public:
    // Getter that retrieves the color      
    bb::cascades::Color niceColor();

private:
    // Variable for the color
    bb::cascades::Color m_nicecolor;

Create the color and expose it to QML:

m_nicecolor = Color::fromARGB(0xff00a8df);
qml->setContextProperty("MyApp", this);

Reference the color in QML:

Container {
    background: MyApp.niceColor
}

Consider these additional optimizations

If you're still experiencing some performance issues, here are a few more strategies that you can follow to improve performance.

Create the UI using C++ instead of QML

When creating an application, it's almost always recommended that you use QML for the UI. However, creating UI components using C++ is slightly faster. The time it takes to parse and process a QML file is slightly more than that for compiled C++ code. If you absolutely need to squeeze that last bit of performance out of your application, then you might want to create your UI with C++ instead of QML.

Remove usage of stderr and stdout

Although stderr and stdout are very useful for debugging your application, usage of these output streams should be removed before you package your application for release. These operations are very costly to run and can negatively impact the performance of your application.

Last modified: 2014-11-17



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus