Table of Contents

Storage of QML defined properties explained (Part 1)

It all started with a simple question:

How much does a property you define in QML code cost in terms of memory? 

This led me down a merry chase into the source of the QML engine. The result is this article and in the end a contribution to Qt5.6.

Before we get started, let’s do a quick recap of what we know about Qt properties on the C++ side. Qt has compile time properties which can be added to QObject derived classes. The various methods associated with such a property (read/write/reset/notify/…) are specified using the Q_PROPERTY macro. The properties themselves are typically stored as C++ member variables. They integrate with the meta object system and are therefore also accessible from the QML-side.

Back to the initial question: how much memory is needed for a QML defined property? To answer this, one first needs to figure out how and where they are

stored. The QQmlVMEMetaObject class, which is instantiated for each QML object, takes care of that.

The constructor of QQmlVMEMetaObject contains this line.

data = new QQmlVMEVariant[metaData->propertyCount - metaData->varPropertyCount];

This gives a hint that we will probably have to differentiate between typed (non-var) and var properties.

Let’s look at typed properties first. So for each typed property the QQmlVMEMetaObject allocates a QQmlVMEVariant. The QQmlVMEVariant class is local to the translation unit of the QQmlVMEMetaObject. 

Let’s have a look there.

class QQmlVMEVariant
    // [...]
    inline QObject *asQObject();
    inline const QVariant &asQVariant();
    inline int asInt();
    inline bool asBool();
    inline double asDouble();
    inline const QString &asQString();
    inline const QUrl &asQUrl();
    inline const QTime &asQTime();
    // [...]
    inline void setValue(QObject *v, QQmlVMEMetaObject *target, int index);
    inline void setValue(const QVariant &);
    inline void setValue(int);
    inline void setValue(bool);
    inline void setValue(double);
    inline void setValue(const QString &);
    inline void setValue(const QUrl &);
    inline void setValue(const QTime &);
    // [...]
    int type;
    void *data[8]; // Large enough to hold all types
    // [...]

This looks promising. The interface contains various methods to get/set types which are also basic QML types. Let’s see where this is used by looking at the asBool()/setValue(bool) methods.

This leads us to:

int QQmlVMEMetaObject::metaCall(QMetaObject::Call c, int _id, void **a)

Assumption: To read for example a bool typed property, metaCall() is invoked with c == QMetaObject::ReadProperty. _id contains the index of the property in the array of QQmlVMEVariant we allocated earlier (and assigned to the data member variable). a is the destination where the property value should be read into.

if (c == QMetaObject::ReadProperty) {
    switch(t) {
    // [...]
    case QVariant::Bool:
        *reinterpret_cast(a[0]) = data[id].asBool();
    // [...]

By setting a breakpoint and loading a simple test QML we can quickly validate that this is actually the right place (the write case is very similar so I will leave it as an exercise for the reader).

For brevity I will leave out the implementation for asBool()/setValue(bool) but if you have a look at it you will notice, that it stores the bool value in these two members of the QQmlVMEVariant:

    int type;
    void *data[8]; // Large enough to hold all types

This allows us to already answer the initial question. How much memory does it take to store a simple QML defined property?

The answer is sizeof(void*)*8 + sizeof(int), which is 36 bytes on a 32bit system and 68 bytes on 64bit system. Taking into account structure padding at the end of the QQmlVMEVariant this will end up at 72 bytes on a 64bit system.

This is quite a bit more than we would have expected. The next part of this series will explain why the QQmlVMEVariant is that large, how var properties are handled and how the current situation can be improved.

Sorry, comments are disabled on this post.

Frank Meerkötter

Frank Meerkötter

Frank Meerkoetter is the Development Lead for basysKom GmbH, where he is consulting customers on industrial and embedded applications, often in combination with Qt. He is responsible for the technical consulting, system- and software-architecture within basysKom. He is the maintainer of Qt OPC UA and a contributor to the Qt project. He has a strong background in Embedded Linux, systems programming, distributed systems and application development. He holds a Master of Computer Science from the University of Applied Sciences in Darmstadt.
Share on facebook
Share on twitter
Share on linkedin
Share on reddit
Share on xing
Share on email
Share on stumbleupon
Share on whatsapp
Share on pocket

Read more

Jeremias Bosch
Awesome :-) The basysKom Toolbox

We would like to introduce our basysKom Toolbox to you. It is a state-of-the-art collection of best practices in agile management and software development and a valuable tool for every kickoff meeting.

Come and visit us on the Embedded World 2020 and take your own copy of the printed card deck with you!

Read More »