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 arestored. 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
{
public:
// [...]
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 &);
// [...]
private:
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();
break; //
[...]
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.