C

Qt Quick Ultralite Performance Guide

Before applying any of the optimizations described in this section, it is important to gather important performance metrics such as CPU usage, memory consumption, application ROM footprint and so on. See Qt Quick Ultralite Performance Logging for more information. This page describes different optimization techniques to improve performance and reduce memory footprint of the application. Besides these optimization techniques, you should also follow the best practices while writing QML code.

Note: Since Qt for MCUs 2.6 the shipped Qt Quick Ultralite Core and Platform libraries are built with QUL_ENABLE_PERFORMANCE_LOGGING and QUL_ENABLE_HARDWARE_PERFORMANCE_LOGGING enabled by default. While useful to profile your application, performance metrics collection adds an unwanted overhead for production ready applications. To remove it, rebuild the Qt Quick Ultralite libraries with QUL_ENABLE_PERFORMANCE_LOGGING and QUL_ENABLE_HARDWARE_PERFORMANCE_LOGGING disabled.

Memory optimization

Flash and RAM footprints can be reduced by using one or more of the following optimization features or techniques:

Enabling resource compression

PNG compression

If ImageFiles.MCU.resourceCompression is set, an image is stored with PNG compression and will be decompressed into the cache when needed. This reduces the size of the binary but adds decompression overhead. See ImageFiles.MCU.resourceCompression to learn more about enabling and using PNG compression in Qt Quick Ultralite applications.

RLE compression

Unlike images using ImageFiles.MCU.resourceCompression, images with pixel formats RGB888RLE, XRGB8888RLE, and ARGB888RLE are decoded on the fly during rendering, and thus don't require any additional storage. The limitation is that scales, rotations, shears, and perspective transforms are not supported for these RLE pixel formats.

See Lossless compressed image formats for more information.

Framebuffer size

Framebuffer size can be optimized by choosing lower color-depth and/or single buffering. Refer to the Framebuffer Requirements page for detailed information about the framebuffer size requirements.

You can also improve performance using the hardware layers if your platform supports it. Refer to Improving performance using hardware layers for more information.

Font quality

The font.quality property allows more fine-grained optimization, by providing hints to render low-quality glyphs. To save memory consumed by glyphs, set the font quality in your QML code to Font.QualityVeryLow.

Refer to MCU.Config.defaultFontQuality for more information.

Omitting source language strings from translations

In some use cases, the source language strings of translations are not rendered at all. Especially when using ID-based translations, the application is not expected to display the source strings. The TranslationFiles.MCU.omitSourceLanguage property enables excluding the source strings from the application binary.

Application performance

Selecting a framebuffer strategy

Qt Quick Ultralite supports both single and double buffering strategies, offering a choice of better performance over memory usage. The single buffer strategy uses less memory but may come with performance overhead, whereas the double buffer strategy uses more memory but offers better performance.

See Framebuffering strategies for more information.

Selecting a font engine

Qt Quick Ultralite provides two font engine options: Monotype Spark and Static font engines.

Monotype font engine

  • Better support for internationalized applications that use a wide range of characters.
  • Better support for languages that require text shaping to ensure correct layout.
  • Runtime support for text scaling, without compromising on performance and text quality.

Static font engine

  • Better support for applications that do not use complex text and has lower footprint.
  • Operates on static precomputed data, enabling very fast lookup operations.
  • Processes font at build time, resulting in no runtime overhead.

Refer to the Feature comparison table for detailed comparison between the two font engines.

Using the Benchmark mode

You can use the Benchmark mode in an application to measure some key performance parameters when the application is running.

These performance parameters include:

ParameterDescription
1Total framesTotal number of frames in the recording interval.
2Average FPSAverage frames per second value measured during the recording interval.
3Minimum FPSMinimum frames per second value captured during the recording.
4Maximum heap usageMaximum heap usage in bytes recorded since the application was started.
5Maximum stack usageMaximum stack usage in bytes recorded since the application was started.
6Average CPU loadCPU Load in percentage value averaged over the recording interval.

The Benchmark mode involves running the application for a specified time and displaying the results at the end of this interval.

The Benchmark mode uses the QulPerf QML type to start and stop the recording of performance parameters.

For a demonstration of how to use the Benchmark mode, see the Qt Quick Ultralite Thermostat Demo and Qt Quick Ultralite Automotive Cluster Demo.

Using cache

Application performance can be enhanced by caching images and text.

Image caching

QmlProject property ImageFiles.MCU.resourceCachePolicy can be used to set the caching policy for image resources.

Note: Source file properties must be set before adding image files to the application using the ImageFiles.files QmlProject property.

Font cache priming

If application contains a lot of text, it affects the application startup significantly. The font cache can be prepopulated at build time to improve startup time of the application.

Note: Cache priming is only supported by Monotype Spark font engine.

See Cache priming for more information.

Text caching

Text rendering performance can be improved further if text cache is enabled for the Qt Quick Ultralite application. On enabling the text cache, pixel data for each text element is cached, reducing the number of calls to the drawing engine.

Refer to Text caching section which explains in detail about text caching feature and how to enable it.

Using hardware layers

On the MCU platforms that support hardware layers, performance can be enhanced by using multiple framebuffers. Hardware layer capabilities of the MCU can be used to blend these framebuffers together and produce the final image to be displayed. Using hardware layers can help reduce footprint and rendering time.

See Improving performance using hardware layers for more information about hardware layer usage.

QML best practices

The way the QML code is written can have a significant impact on the flash and random access memory footprint of the application. In general, avoiding duplication in the QML code will also reduce the amount of C++ code that is generated and reduce the flash memory footprint. There are also some other things to watch out for to prevent high memory usage and improve performance.

Create reusable components

Instead of repeating the same code patterns in a lot of places in the code, try to encapsulate the pattern in a separate QML file whenever possible.

For example, the code below has a list of labels, each containing some text and an image:

Column {
    spacing: 15
    anchors.centerIn: parent

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 1"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img1.png"
            }
        }
    }

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 2"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img2.png"
            }
        }
    }
}

This code can be simplified by creating a separate QML file called Label.qml, with the adjustable parts exposed as properties or property aliases as shown here:

import QtQuick 2.15

Rectangle {
    property alias imageSource: imageItem.source
    property alias text: textItem.text

    width: 250
    height: 120
    color: "#e7e3e7"

    Row {
        anchors.centerIn: parent
        spacing: 10

        Text {
            id: textItem
            anchors.verticalCenter: parent.verticalCenter
            color: "#22322f"
            font.pixelSize: 22
        }

        Image {
            id: imageItem
            anchors.verticalCenter: parent.verticalCenter
        }
    }
}

This QML component can then be reused in the original QML code, avoiding duplication:

Column {
    spacing: 15
    anchors.centerIn: parent

    Label {
        text: "Entry 1"
        imageSource: "img1.png"
    }

    Label {
        text: "Entry 2"
        imageSource: "img2.png"
    }
}

Limiting PropertyChanges

A QML file with a lot of states and many properties that are affected by those states through PropertyChanges, leads to generating large and complex C++ code. The size of the generated code will be N x M, where N is the number of states and M is the number of unique properties updated by PropertyChanges in those states.

Here's an example with just two states and two properties, but imagine that there would be a lot more similar states to choose between a variety of views in the same QML component:

Item {
    state: "0"
    states: [
        State {
            name: "0"
            PropertyChanges { target: viewA; visible: true }
        },
        State {
            name: "1"
            PropertyChanges { target: viewB; visible: true }
        }
    ]
    ViewA {
        id: viewA
        visible: false
    }
    ViewB {
        id: viewB
        visible: false
    }
}

This can be optimized by setting the visible property of the views directly based on the state:

Item {
    id: root
    state: "0"
    states: [
        State { name: "0" },
        State { name: "1" }
    ]
    ViewA {
        id: viewA
        visible: root.state == "0"
    }
    ViewB {
        id: viewB
        visible: root.state == "1"
    }
}

Avoid empty container items

The Item type can be useful for grouping other items, making it possible to set their visibility and position in a combined way. Limit the use of container items as they increase the memory usage. For example, the outer Item in the code snippet below is unnecessary:

Item {
    Image {
        anchors.fill: parent
        source: "img.png"
    }
}

It serves no good purpose in this case, as the contained Image item can be used directly instead:

Image {
    anchors.fill: parent
    source: "img.png"
}

Save RAM by loading components dynamically

Your application might contain QML components with multiple complex items that consist of many sub-items, which are visible at different times. You can reduce RAM usage of such items by dynamically loading them, using Loader API.

Unload existing hidden items explicitly before loading new items, to avoid memory peaks. Depending on the applications UI design and memory constraints, ensure that only a select no. of items are loaded at any given time. The following example demonstrates how you can ensure only one page in the SwipeView is loaded into memory at any given time:

SwipeView {
    id: theSwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    function updateLoaderStates() {
        console.log("updating loader states ...")
        if (theSwipe.currentIndex === 0) {
            loader1.source = ""
            loader0.source = "FirstPage.qml"
        } else if (theSwipe.currentIndex === 1) {
            loader0.source = ""
            loader1.source = "SecondPage.qml"
        }
    }

    Component.onCompleted: updateLoaderStates()
    onCurrentIndexChanged: updateLoaderStates()

    Loader {
        id: loader0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader {
        id: loader1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

As a general rule, do not rely on the evaluation order of bindings. In the following example, you have no control on the order in which the items are loaded and unloaded. This may result in temporarily allocating memory for both pages of the application:

SwipeView {
    id: mySwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    onCurrentIndexChanged: {
        console.log("index changed ...")
    }

    Loader
    {
        source: "FirstPage.qml"
        active: mySwipe.currentIndex === 0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader
    {
        source: "SecondPage.qml"
        active: mySwipe.currentIndex === 1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

Reduce the number of visual components

Each visual item typically carries some processing and rendering overhead at runtime. When possible, try reduce the number of visual items required to compose the UI. Below are some examples of how this can be done.

Reduce overlapping images

If two images are always overlapping in the UI, it might be better to combine them into a single image. A lot of overlapping images can reduce performance and will consume more memory. For example, the inner.png in the following code snippet is smaller than the outer.png image:

Image {
    id: outer
    source: "outer.png"
}
Image {
    anchors.centerIn: outer
    source: "inner.png"
}

Instead, combine inner.png and outer.png into a single image:

Image {
    source: "combined.png"
}

If some static text is also overlapping the image, it is worth combining that into the image, instead of using separate Text or StaticText items.

Reduce the Text items

Avoid arranging multiple Text items in a row if they can be combined into a single Text item. For example, the following code snippet shows two Text items arranged in a row:

Row {
    Text {
        text: "Temperature: "
    }
    Text {
        text: root.temperature
    }
}

They can be combined into a single Text item:

Text {
    text: "Temperature: " + root.temperature
}

Available under certain Qt licenses.
Find out more.