Auf dieser Seite

Überlegungen zur Leistung und Empfehlungen

Überlegungen zum Timing

Als Anwendungsentwickler sind Sie in der Regel bestrebt, der Rendering-Engine eine konstante Bildwiederholrate von 60 Bildern pro Sekunde zu ermöglichen. Je nach Hardware und Anforderungen kann dieser Wert zwar variieren, doch 60 FPS sind sehr verbreitet. 60 FPS bedeuten, dass zwischen jedem Frame etwa 16 Millisekunden Zeit für die Verarbeitung zur Verfügung stehen – einschließlich der Verarbeitung, die erforderlich ist, um die Zeichenprimitive an die Grafikhardware zu übertragen.

In der Praxis bedeutet dies, dass der Anwendungsentwickler:

  • wo immer möglich, asynchrone, ereignisgesteuerte Programmierung verwenden
  • Worker-Threads für umfangreiche Verarbeitungsaufgaben nutzen
  • die Ereignisschleife niemals manuell ausführen
  • innerhalb blockierender Funktionen nie mehr als ein paar Millisekunden pro Bild aufwenden

Andernfalls kommt es zu übersprungenen Frames, was drastische Auswirkungen auf das Benutzererlebnis hat.

Hinweis: Ein Muster, das zwar verlockend ist, aber niemals verwendet werden sollte, besteht darin, eine eigene „ QEventLoop “ zu erstellen oder „ QCoreApplication::processEvents()“ aufzurufen, um Blockierungen innerhalb eines Backend-Codeblocks – beispielsweise in C++, der von QML aus aufgerufen wird – zu vermeiden. Dies ist gefährlich, da die QML-Engine beim Eintritt in eine Ereignisschleife in einem Signal-Handler oder einer Bindung weiterhin andere Bindungen, Animationen, Übergänge usw. ausführt. Diese Bindungen können dann Nebenwirkungen verursachen, die beispielsweise die Hierarchie zerstören, in der sich Ihre Ereignisschleife befindet.

Profiling

Der wichtigste Tipp lautet: Verwenden Sie QML Profiler die in Qt Creator enthaltene Funktion (oder einen Trace-Viewer unter Qt Extension for Visual Studio Code). Wenn Sie wissen, wo in einer Anwendung Zeit verbraucht wird, können Sie sich auf tatsächlich vorhandene Problembereiche konzentrieren, anstatt auf solche, die möglicherweise bestehen. Weitere Informationen finden Sie unter Qt Creator: Profiling QML Applications und Qt Extension for Visual Studio Code: Profile QML code.

Wenn Sie ermitteln, welche Bindungen am häufigsten ausgeführt werden oder in welchen Funktionen Ihre Anwendung die meiste Zeit verbringt, können Sie entscheiden, ob Sie die Problembereiche optimieren oder einige Implementierungsdetails Ihrer Anwendung neu gestalten müssen, um die Leistung zu verbessern. Der Versuch, Code ohne Profiling zu optimieren, führt wahrscheinlich eher zu sehr geringen als zu signifikanten Leistungsverbesserungen.

JavaScript-Code

Die meisten QML-Anwendungen enthalten JavaScript-Code in Form von Eigenschaftsbindungsausdrücken, Funktionen und Signal-Handlern. Dies stellt im Allgemeinen kein Problem dar. Dank fortschrittlicher Werkzeuge wie dem „Qt Quick “-Compiler können einfache Funktionen und Bindungen sehr schnell ausgeführt werden. Es muss jedoch darauf geachtet werden, dass keine unnötigen Verarbeitungsschritte versehentlich ausgelöst werden. Der QML Profiler kann ausführliche Details zur JavaScript-Ausführung und zu deren Auslösern anzeigen.

Typkonvertierung

Ein wesentlicher Aufwand bei der Verwendung von JavaScript besteht darin, dass in manchen Fällen beim Zugriff auf eine Eigenschaft eines QML-Typs ein JavaScript-Objekt mit einer externen Ressource erstellt wird, die die zugrunde liegenden C++-Daten (oder einen Verweis darauf) enthält. In den meisten Fällen ist dies relativ ressourcenschonend, in anderen Fällen kann es jedoch sehr ressourcenintensiv sein. Bei der Behandlung großer und komplizierter Werttypen oder Sequenztypen ist Vorsicht geboten. Diese müssen von der QML-Engine kopiert werden, wann immer Sie sie an Ort und Stelle ändern oder einer anderen Eigenschaft zuweisen. Wenn dies zu einem Engpass wird, sollten Sie stattdessen die Verwendung von Objekttypen in Betracht ziehen. Listen von Objekttypen weisen nicht dasselbe Problem auf wie Listen von Wertetypen, da Listen von Objekttypen mithilfe von ` QQmlListProperty` implementiert werden.

Die meisten Konvertierungen zwischen einfachen Werttypen sind ressourcenschonend. Es gibt jedoch Ausnahmen. Das Erstellen einer URL aus einer Zeichenkette kann die Erstellung einer „ QUrl “-Instanz erfordern, was ressourcenintensiv ist.

Auflösen von Eigenschaften

Das Auflösen von Eigenschaften nimmt Zeit in Anspruch. Zwar sind Suchvorgänge in der Regel so optimiert, dass sie bei nachfolgenden Ausführungen deutlich schneller ablaufen, doch ist es immer am besten, unnötigen Aufwand nach Möglichkeit gänzlich zu vermeiden.

Im folgenden Beispiel haben wir einen Codeblock, der häufig ausgeführt wird (in diesem Fall handelt es sich um den Inhalt einer expliziten Schleife; es könnte sich beispielsweise aber auch um einen häufig ausgewerteten Bindungsausdruck handeln), und darin lösen wir das Objekt mit der ID „rect“ und dessen Eigenschaft „color“ mehrfach auf:

// bad.qml
import QtQuick

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which: string, value: real) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            printValue("red", rect.color.r);
            printValue("green", rect.color.g);
            printValue("blue", rect.color.b);
            printValue("alpha", rect.color.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}

Jedes Mal, wenn „ rect.color “ abgerufen wird, muss die QML-Engine:

  • einen Werttyp-Wrapper auf dem JavaScript-Heap zuweisen.
  • den Getter der Eigenschaft „ color “ von ` Rectangle` ausführen.
  • Das resultierende ` QColor ` in den Werttyp-Wrapper kopieren.

Wir müssen dies nicht viermal tun. Stattdessen können wir die gemeinsame Basis nur einmal im Block auflösen:

// good.qml
import QtQuick

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which: string, value: real) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            var rectColor = rect.color; // resolve the common base.
            printValue("red", rectColor.r);
            printValue("green", rectColor.g);
            printValue("blue", rectColor.b);
            printValue("alpha", rectColor.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}

Schon diese einfache Änderung führt zu einer erheblichen Leistungssteigerung. Beachten Sie, dass der obige Code noch weiter verbessert werden kann (da sich die gesuchte Eigenschaft während der Schleifenverarbeitung nie ändert), indem die Eigenschaftsauflösung wie folgt aus der Schleife herausgelagert wird:

// better.qml
import QtQuick

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which: string, value: real) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        var rectColor = rect.color; // resolve the common base outside the tight loop.
        for (var i = 0; i < 1000; ++i) {
            printValue("red", rectColor.r);
            printValue("green", rectColor.g);
            printValue("blue", rectColor.b);
            printValue("alpha", rectColor.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}

Eigenschaftsbindungen

Ein Eigenschaftsbindungsausdruck wird neu ausgewertet, wenn sich eine der Eigenschaften, auf die er verweist, ändert. Daher sollten Bindungsausdrücke so einfach wie möglich gehalten werden.

Wenn Sie eine Schleife haben, in der Sie eine Verarbeitung durchführen, aber nur das Endergebnis der Verarbeitung wichtig ist, ist es oft besser, einen temporären Akkumulator zu aktualisieren, den Sie anschließend der zu aktualisierenden Eigenschaft zuweisen, anstatt die Eigenschaft selbst schrittweise zu aktualisieren, um zu vermeiden, dass Bindungsausdrücke während der Zwischenphasen der Akkumulation neu ausgewertet werden.

Das folgende konstruierte Beispiel veranschaulicht diesen Punkt:

// bad.qml
import QtQuick

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        for (var i = 0; i < someData.length; ++i) {
            accumulatedValue = accumulatedValue + someData[i];
        }
    }
}

Die Schleife im onCompleted-Handler bewirkt, dass die Eigenschaftsbindung „text“ sechsmal neu ausgewertet wird (was wiederum dazu führt, dass alle anderen Eigenschaftsbindungen, die auf dem Textwert beruhen, sowie der onTextChanged-Signal-Handler jedes Mal neu ausgewertet werden und der Text jedes Mal für die Anzeige formatiert wird). Dies ist in diesem Fall eindeutig unnötig, da uns eigentlich nur der Endwert der Akkumulation interessiert.

Der Code könnte wie folgt umgeschrieben werden:

// good.qml
import QtQuick

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        var temp = accumulatedValue;
        for (var i = 0; i < someData.length; ++i) {
            temp = temp + someData[i];
        }
        accumulatedValue = temp;
    }
}

Tipps zu Sequenzen

Wie bereits erwähnt, müssen Sequenzen von Werttypen mit Bedacht behandelt werden.

Erstens zeigen Sequenztypen in zwei unterschiedlichen Szenarien ein unterschiedliches Verhalten:

  • wenn die Sequenz eine „ Q_PROPERTY “ eines „ QObject “ ist (wir nennen dies eine Referenzsequenz),
  • wenn die Sequenz von einer ` Q_INVOKABLE `-Funktion eines ` QObject ` zurückgegeben wird (wir nennen dies eine Kopiersequenz).

Eine Referenzsequenz wird bei jeder Änderung – sei es in Ihrem JavaScript-Code oder am ursprünglichen Objekt – über den ` QMetaObject ` gelesen und geschrieben. Zur Optimierung können Referenzsequenzen (ebenso wie Referenzwerttypen) verzögert geladen werden. Der eigentliche Inhalt wird dann erst bei der ersten Verwendung abgerufen. Das bedeutet, dass das Ändern des Werts eines beliebigen Elements in der Sequenz aus JavaScript heraus Folgendes zur Folge hat:

  • Möglicherweise wird der Inhalt aus dem „ QObject “ gelesen (falls verzögert geladen).
  • Änderung des Elements am angegebenen Index in dieser Sequenz.
  • Das Zurückschreiben der gesamten Sequenz in die „ QObject “.

Eine Kopiersequenz ist weitaus einfacher, da die eigentliche Sequenz in den Ressourcendaten des JavaScript-Objekts gespeichert ist, sodass kein Lese-/Änderungs-/Schreibzyklus stattfindet (stattdessen werden die Ressourcendaten direkt geändert).

Daher sind Schreibvorgänge auf Elemente einer Referenzsequenz deutlich langsamer als Schreibvorgänge auf Elemente einer Kopierssequenz. Tatsächlich entspricht das Schreiben in ein einzelnes Element einer Referenzsequenz mit N Elementen vom Aufwand her der Zuweisung einer Kopierssequenz mit N Elementen an diese Referenzsequenz; daher ist es in der Regel besser, während der Berechnung eine temporäre Kopierssequenz zu ändern und das Ergebnis anschließend einer Referenzsequenz zuzuweisen.

Nehmen wir an, der folgende C++-Typ existiert (und wurde zuvor im Namespace „Qt.example“ registriert):

class SequenceTypeExample : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged)

public:
    SequenceTypeExample() : QQuickItem() { m_list << 1.1 << 2.2 << 3.3; }
    ~SequenceTypeExample() {}

    QList<qreal> qrealListProperty() const { return m_list; }
    void setQrealListProperty(const QList<qreal> &list) { m_list = list; emit qrealListPropertyChanged(); }

signals:
    void qrealListPropertyChanged();

private:
    QList<qreal> m_list;
};

Das folgende Beispiel schreibt in einer engen Schleife in Elemente einer Referenzsequenz, was zu schlechter Leistung führt:

// bad.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        qrealListProperty.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                qrealListProperty[j] = j;
            }
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

Das Lesen und Schreiben der Eigenschaft „ QObject “ in der inneren Schleife, das durch den Ausdruck „ "qrealListProperty[j] = j" “ verursacht wird, macht diesen Code sehr suboptimal. Stattdessen wäre Folgendes funktional gleichwertig, aber wesentlich schneller:

// good.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        var someData = [1.1, 2.2, 3.3]
        someData.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                someData[j] = j;
            }
            qrealListProperty = someData;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

Ein weiteres gängiges Muster, das vermieden werden sollte, sind „Read-Modify-Write“-Schleifen, bei denen jedes Element gelesen, geändert und wieder in die Sequenz-Eigenschaft geschrieben wird. Ähnlich wie im vorherigen Beispiel führt dies bei jeder Iteration zu Lese- und Schreibvorgängen der „ QObject “-Eigenschaft:

// bad.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        qrealListProperty.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                qrealListProperty[j] = qrealListProperty[j] * 2;
            }
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

Erstellen Sie stattdessen eine manuelle Kopie der Sequenz, ändern Sie die Kopie und weisen Sie das Ergebnis anschließend wieder der Eigenschaft zu:

// good.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 500; ++i) {
            let data = [...qrealListProperty];
            for (var j = 0; j < 100; ++j) {
                data[j] = data[j] * 2;
            }
            qrealListProperty = data;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

Zweitens wird ein Änderungssignal für die Eigenschaft ausgegeben, sobald sich ein Element darin ändert. Wenn Sie viele Bindungen zu einem bestimmten Element in einer Sequenz-Eigenschaft haben, ist es besser, eine dynamische Eigenschaft zu erstellen, die an dieses Element gebunden ist, und diese dynamische Eigenschaft anstelle des Sequenzelements als Symbol in den Bindungsausdrücken zu verwenden, da dies nur dann eine Neubewertung der Bindungen auslöst, wenn sich ihr Wert ändert.

Dies ist ein ungewöhnlicher Anwendungsfall, auf den die meisten Kunden wohl nie stoßen werden, den man jedoch im Hinterkopf behalten sollte, falls Sie einmal etwas Ähnliches tun sollten:

// bad.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root

    property int firstBinding: qrealListProperty[1] + 10;
    property int secondBinding: qrealListProperty[1] + 20;
    property int thirdBinding: qrealListProperty[1] + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

Beachten Sie, dass, obwohl in der Schleife nur das Element am Index 2 geändert wird, alle drei Bindungen neu ausgewertet werden, da das Änderungssignal so detailliert ist, dass es angibt, dass sich die gesamte Eigenschaft geändert hat. Daher kann es manchmal von Vorteil sein, eine Zwischenbindung hinzuzufügen:

// good.qml
import QtQuick
import Qt.example

SequenceTypeExample {
    id: root

    property int intermediateBinding: qrealListProperty[1]
    property int firstBinding: intermediateBinding + 10;
    property int secondBinding: intermediateBinding + 20;
    property int thirdBinding: intermediateBinding + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}

Im obigen Beispiel wird jedes Mal nur die Zwischenbindung neu ausgewertet, was zu einer deutlichen Leistungssteigerung führt.

Tipps zu Werttypen

Eigenschaftenvon Werttypen (font, color, vector3d usw.) weisen eine ähnliche Semantik hinsichtlich der Eigenschaft „ QObject “ und der Änderungsbenachrichtigung auf wie Eigenschaften von Sequenztypen. Daher gelten die oben für Sequenzen gegebenen Tipps auch für Eigenschaften von Werttypen. Zwar stellen sie bei Werttypen in der Regel ein geringeres Problem dar (da die Anzahl der Untereigenschaften eines Werttyps meist weitaus geringer ist als die Anzahl der Elemente in einer Sequenz), doch wirkt sich jede Zunahme der Anzahl unnötig neu ausgewerteter Bindungen negativ auf die Leistung aus.

Allgemeine Tipps zur Leistungsoptimierung

Allgemeine Überlegungen zur JavaScript-Leistung, die sich aus dem Sprachdesign ergeben, gelten auch für QML. Vor allem:

  • Vermeiden Sie die Verwendung von `eval()`, sofern dies möglich ist
  • Löschen Sie keine Eigenschaften von Objekten

Gängige Schnittstellenelemente

Textelemente

Die Berechnung von Textlayouts kann sehr zeitaufwendig sein. Verwenden Sie nach Möglichkeit das Format „ PlainText “ anstelle von „ StyledText “, da dies den Arbeitsaufwand für die Layout-Engine verringert. Wenn Sie „ PlainText “ nicht verwenden können (z. B. weil Sie Bilder einbetten müssen oder Tags verwenden, um bestimmte Zeichenbereiche mit einer bestimmten Formatierung (fett, kursiv usw.) zu versehen, anstatt den gesamten Text zu formatieren), sollten Sie „ StyledText “ verwenden.

Sie sollten „ AutoText “ nur verwenden, wenn der Text möglicherweise (was jedoch unwahrscheinlich ist) „ StyledText “ ist, da dieser Modus einen zusätzlichen Parsing-Aufwand verursacht. Der Modus „ RichText “ sollte nicht verwendet werden, da „ StyledText “ fast alle dessen Funktionen zu einem Bruchteil der Kosten bereitstellt.

Bilder

Bilder sind ein wesentlicher Bestandteil jeder Benutzeroberfläche. Leider sind sie aufgrund der Ladezeit, des Speicherbedarfs und der Art und Weise, wie sie verwendet werden, auch eine große Problemquelle.

Asynchrones Laden

Bilder sind oft recht groß, daher ist es ratsam, sicherzustellen, dass das Laden eines Bildes den UI-Thread nicht blockiert. Setzen Sie die Eigenschaft „asynchronous“ des QML-Image-Elements auf „ true “, um das asynchrone Laden von Bildern aus dem lokalen Dateisystem zu aktivieren (Remote-Bilder werden immer asynchron geladen), sofern dies keine negativen Auswirkungen auf die Ästhetik der Benutzeroberfläche hat.

Bildelemente, bei denen die Eigenschaft „asynchronous“ auf „ true “ gesetzt ist, laden Bilder in einem Worker-Thread mit niedriger Priorität.

Explizite Quellgröße

Wenn Ihre Anwendung ein großes Bild lädt, es jedoch in einem kleinen Element anzeigt, setzen Sie die Eigenschaft „sourceSize“ auf die Größe des gerenderten Elements, um sicherzustellen, dass die verkleinerte Version des Bildes im Speicher verbleibt und nicht die große.

Beachten Sie, dass eine Änderung der „sourceSize“-Eigenschaft dazu führt, dass das Bild neu geladen wird.

Vermeiden Sie die Komposition zur Laufzeit

Denken Sie außerdem daran, dass Sie die Komposition zur Laufzeit vermeiden können, indem Sie die vorkomponierte Bildressource in Ihrer Anwendung bereitstellen (z. B. Elemente mit Schatteneffekten).

Vermeiden Sie die Glättung von Bildern

Aktivieren Sie „ image.smooth “ nur, wenn dies erforderlich ist. Auf manchen Hardwarekomponenten verlangsamt dies die Ausführung, und es hat keinen visuellen Effekt, wenn das Bild in seiner natürlichen Größe angezeigt wird.

Zeichnen

Vermeiden Sie es, denselben Bereich mehrmals zu zeichnen. Verwenden Sie „Item“ als Stammelement anstelle von „Rectangle“, um zu vermeiden, dass der Hintergrund mehrmals gezeichnet wird.

Elemente mit Ankerpunkten positionieren

Es ist effizienter, Anker anstelle von Bindungen zu verwenden, um Elemente relativ zueinander zu positionieren. Betrachten Sie diese Verwendung von Bindungen, um „rect2“ relativ zu „rect1“ zu positionieren:

Rectangle {
    id: rect1
    x: 20
    width: 200; height: 200
}
Rectangle {
    id: rect2
    x: rect1.x
    y: rect1.y + rect1.height
    width: rect1.width - 20
    height: 200
}

Dies lässt sich mit Ankerpunkten effizienter erreichen:

Rectangle {
    id: rect1
    x: 20
    width: 200; height: 200
}
Rectangle {
    id: rect2
    height: 200
    anchors.left: rect1.left
    anchors.top: rect1.bottom
    anchors.right: rect1.right
    anchors.rightMargin: 20
}

Die Positionierung mit Bindungen (durch Zuweisung von Bindungsausdrücken zu den Eigenschaften „x“, „y“, „width“ und „height“ visueller Objekte anstelle der Verwendung von Ankerpunkten) ist relativ langsam, bietet jedoch maximale Flexibilität.

Wenn das Layout nicht dynamisch ist, lässt sich das Layout am leistungsfähigsten durch statische Initialisierung der Eigenschaften „x“, „y“, „width“ und „height“ festlegen. Die Koordinaten von Elementen beziehen sich immer auf ihr übergeordnetes Element. Wenn Sie also einen festen Versatz gegenüber der Koordinate 0,0 des übergeordneten Elements wünschen, sollten Sie keine Anker verwenden. Im folgenden Beispiel befinden sich die untergeordneten Rectangle-Objekte an derselben Stelle, doch der gezeigte Code mit Ankern ist nicht so ressourcenschonend wie der Code, der eine feste Positionierung durch statische Initialisierung verwendet:

Rectangle {
    width: 60
    height: 60
    Rectangle {
        id: fixedPositioning
        x: 20
        y: 20
        width: 20
        height: 20
    }
    Rectangle {
        id: anchorPositioning
        anchors.fill: parent
        anchors.margins: 20
    }
}

Modelle und Ansichten

Die meisten Anwendungen verfügen über mindestens ein Modell, das Daten an eine Ansicht liefert. Es gibt einige semantische Aspekte, die Anwendungsentwickler beachten müssen, um maximale Leistung zu erzielen.

Benutzerdefinierte C++-Modelle

Häufig ist es wünschenswert, ein eigenes Modell in einer Backend-Sprache wie C++ zu schreiben, um es mit einer Ansicht in QML zu verwenden. Die optimale Implementierung eines solchen Modells hängt zwar stark vom jeweiligen Anwendungsfall ab, für den es bestimmt ist, doch gelten folgende allgemeine Richtlinien:

  • Arbeiten Sie so asynchron wie möglich
  • Führen Sie die gesamte Verarbeitung in einem (niedrigrangigen) Worker-Thread durch
  • Fassen Sie Backend-Operationen zu Batches zusammen, um (möglicherweise langsame) E/A- und IPC-Vorgänge zu minimieren

Es ist wichtig zu beachten, dass die Verwendung eines Worker-Threads mit niedriger Priorität empfohlen wird, um das Risiko einer Unterversorgung des GUI-Threads zu minimieren (was zu einer schlechteren wahrgenommenen Leistung führen könnte). Denken Sie außerdem daran, dass Synchronisations- und Sperrmechanismen eine wesentliche Ursache für langsame Leistung sein können; daher sollte darauf geachtet werden, unnötige Sperren zu vermeiden.

QML-Typ „ListModel“

Qt Qml „Models“ stellt einen „ ListModel “-Typ bereit, der verwendet werden kann, um Daten an einen „ ListView “ zu übergeben. Er eignet sich für schnelles Prototyping, ist jedoch für größere Datenmengen nicht geeignet. Verwenden Sie bei Bedarf einen geeigneten „ QAbstractItemModel “.

Befüllen innerhalb eines Worker-Threads

ListModel Elemente können in einem (niedrigrangigen) Worker-Thread in JavaScript gefüllt werden. Der Entwickler muss „ sync() “ explizit innerhalb des „ WorkerScript “ auf das „ ListModel “ aufrufen, damit die Änderungen mit dem Hauptthread synchronisiert werden. Weitere Informationen finden Sie in der Dokumentation unter „ WorkerScript “.

Bitte beachten Sie, dass die Verwendung eines „ WorkerScript “-Elements zur Erstellung einer separaten JavaScript-Engine führt (da die JavaScript-Engine pro Thread läuft). Dies führt zu einem erhöhten Speicherbedarf. Mehrere „ WorkerScript “-Elemente nutzen jedoch alle denselben Worker-Thread, sodass die Auswirkungen auf den Speicherverbrauch durch die Verwendung eines zweiten oder dritten „ WorkerScript “-Elements vernachlässigbar sind, sobald eine Anwendung bereits eines verwendet. Andererseits werden die zusätzlichen Worker-Skripte jedoch nicht parallel ausgeführt.

Verwenden Sie keine dynamischen Rollen

Das Element „ ListModel “ geht zu Optimierungszwecken davon aus, dass die Rollentypen innerhalb jedes Elements in einem bestimmten Modell stabil sind. Wenn sich der Typ von Element zu Element dynamisch ändern kann, verschlechtert sich die Leistung des Modells erheblich.

Daher ist die dynamische Typisierung standardmäßig deaktiviert; der Entwickler muss die boolesche Eigenschaft „ dynamicRoles “ des Modells ausdrücklich setzen, um die dynamische Typisierung zu aktivieren (und die damit verbundene Leistungseinbuße in Kauf zu nehmen). Wir empfehlen, die dynamische Typisierung nur zu verwenden, wenn dies unbedingt erforderlich ist.

Ansichten

View-Delegaten sollten so einfach wie möglich gehalten werden. Der Delegat sollte gerade so viel QML enthalten, wie zur Anzeige der erforderlichen Informationen nötig ist. Jede zusätzliche Funktionalität, die nicht unmittelbar benötigt wird (z. B. wenn beim Anklicken weitere Informationen angezeigt werden), sollte erst bei Bedarf erstellt werden (siehe den folgenden Abschnitt zur verzögerten Initialisierung).

Die folgende Liste fasst gut zusammen, was beim Entwerfen eines Delegaten zu beachten ist:

  • Je weniger Elemente ein Delegat enthält, desto schneller können diese erstellt werden und desto schneller lässt sich die Ansicht scrollen.
  • Halten Sie die Anzahl der Bindungen in einem Delegaten so gering wie möglich; verwenden Sie insbesondere Anker statt Bindungen für die relative Positionierung innerhalb eines Delegaten.
  • Vermeiden Sie die Verwendung von „ ShaderEffect “-Elementen innerhalb von Delegaten.
  • Aktivieren Sie niemals das Clipping für einen Delegaten.

Sie können die Eigenschaft „ cacheBuffer “ einer Ansicht festlegen, um die asynchrone Erstellung und Pufferung von Delegaten außerhalb des sichtbaren Bereichs zu ermöglichen. Die Verwendung eines „ cacheBuffer “ wird für Ansichtsdelegaten empfohlen, die nicht trivial sind und wahrscheinlich nicht innerhalb eines einzigen Frames erstellt werden.

Beachten Sie, dass ein „ cacheBuffer “ zusätzliche Delegaten im Speicher hält. Daher muss der durch die Verwendung des „ cacheBuffer “ erzielte Nutzen gegen den zusätzlichen Speicherverbrauch abgewogen werden. Entwickler sollten Benchmarks durchführen, um den besten Wert für ihren Anwendungsfall zu ermitteln, da die durch die Verwendung eines „ cacheBuffer “ verursachte erhöhte Speicherbelastung in seltenen Fällen zu einer verringerten Bildrate beim Scrollen führen kann.

Um die Leistung weiter zu verbessern, sollten Sie die Wiederverwendung von Elementen in Ansichten aktivieren. Weitere Informationen finden Sie unter Reusing Items for ListView und Reusing Items for TableView and TreeView.

Visuelle Effekte

Qt Quick enthält mehrere Funktionen, mit denen Entwickler und Designer besonders ansprechende Benutzeroberflächen erstellen können. Flüssige und dynamische Übergänge sowie visuelle Effekte können in einer Anwendung sehr wirkungsvoll eingesetzt werden. Bei der Verwendung einiger Funktionen in QML ist jedoch Vorsicht geboten, da diese Auswirkungen auf die Leistung haben können.

Animationen

Im Allgemeinen führt die Animation einer Eigenschaft dazu, dass alle Bindungen, die auf diese Eigenschaft verweisen, neu ausgewertet werden. In der Regel ist dies erwünscht, doch in anderen Fällen kann es sinnvoller sein, die Bindung vor der Ausführung der Animation zu deaktivieren und sie erst nach Abschluss der Animation wieder zuzuweisen.

Vermeiden Sie die Ausführung von JavaScript während der Animation. So sollte beispielsweise die Ausführung eines komplexen JavaScript-Ausdrucks für jedes Einzelbild einer Animation der Eigenschaft „x“ vermieden werden.

Entwickler sollten bei der Verwendung von Skriptanimationen besonders vorsichtig sein, da diese im Hauptthread ausgeführt werden (und daher dazu führen können, dass Frames übersprungen werden, wenn ihre Ausführung zu lange dauert).

Partikel

Das Qt Quick Particles Modul ermöglicht die nahtlose Integration wunderschöner Partikeleffekte in Benutzeroberflächen. Allerdings verfügt jede Plattform über unterschiedliche Grafikhardware-Fähigkeiten, und das Particles-Modul ist nicht in der Lage, die Parameter auf das zu beschränken, was Ihre Hardware problemlos unterstützen kann. Je mehr Partikel Sie rendern möchten (und je größer diese sind), desto leistungsfähiger muss Ihre Grafikhardware sein, um eine Bildwiederholrate von 60 FPS zu erreichen. Die Steuerung einer größeren Anzahl von Partikeln erfordert eine schnellere CPU. Es ist daher wichtig, alle Partikeleffekte auf Ihrer Zielplattform sorgfältig zu testen, um die Anzahl und Größe der Partikel zu ermitteln, die Sie bei 60 FPS rendern können.

Es ist zu beachten, dass ein Partikelsystem deaktiviert werden kann, wenn es nicht verwendet wird (beispielsweise bei einem nicht sichtbaren Element), um unnötige Simulationen zu vermeiden.

Weitere detaillierte Informationen finden Sie im Leitfaden zur Leistung von Partikelsystemen.

Steuerung der Lebensdauer von Elementen

Durch die Aufteilung einer Anwendung in einfache, modulare Komponenten, die jeweils in einer einzelnen QML-Datei enthalten sind, können Sie schnellere Startzeiten der Anwendung und eine bessere Kontrolle über die Speichernutzung erzielen sowie die Anzahl der aktiven, aber unsichtbaren Elemente in Ihrer Anwendung reduzieren.

Lazy Initialization

Die QML-Engine wendet einige raffinierte Techniken an, um sicherzustellen, dass das Laden und Initialisieren von Komponenten nicht dazu führt, dass Frames übersprungen werden. Es gibt jedoch keinen besseren Weg, die Startzeit zu verkürzen, als unnötige Rechenoperationen zu vermeiden und diese erst dann auszuführen, wenn sie tatsächlich erforderlich sind. Dies lässt sich entweder durch die Verwendung von „ Loader “ erreichen.

Verwendung von „Loader“

Der Loader ist ein Element, das das dynamische Laden und Entladen von Komponenten ermöglicht.

  • Mithilfe der Eigenschaft „active“ eines Loaders kann die Initialisierung so lange verzögert werden, bis sie benötigt wird.
  • Mithilfe der überladenen Version der Funktion „setSource()“ können anfängliche Eigenschaftswerte übergeben werden.
  • Das Setzen der Loader-Eigenschaft „ asynchronous “ auf „true“ kann zudem die Flüssigkeit während der Instanziierung einer Komponente verbessern.

Nicht verwendete Elemente löschen

Elemente, die unsichtbar sind, weil sie Kinder eines nicht sichtbaren Elements sind (beispielsweise die zweite Registerkarte in einem Tab-Widget, während die erste Registerkarte angezeigt wird), sollten in den meisten Fällen verzögert initialisiert und gelöscht werden, sobald sie nicht mehr benötigt werden, um den fortlaufenden Aufwand zu vermeiden, der durch ihre fortgesetzte Aktivität entsteht (z. B. Rendering, Animationen, Auswertung von Eigenschaftsbindungen usw.).

Ein mit einem Loader-Element geladenes Element kann durch Zurücksetzen der Eigenschaft „source“ oder „sourceComponent“ des Loaders freigegeben werden, während andere Elemente explizit durch Aufruf von destroy() freigegeben werden können. In manchen Fällen kann es erforderlich sein, das Element aktiv zu lassen; in diesem Fall sollte es zumindest unsichtbar gemacht werden.

Weitere Informationen zu aktiven, aber unsichtbaren Elementen finden Sie im folgenden Abschnitt zum Thema „Rendering“.

Darstellung

Der für das Rendering verwendete Szenenbaum in Qt Quick ermöglicht die flüssige Darstellung hochdynamischer, animierter Benutzeroberflächen mit 60 FPS. Es gibt jedoch einige Faktoren, die die Rendering-Leistung drastisch beeinträchtigen können, und Entwickler sollten darauf achten, diese Fallstricke nach Möglichkeit zu vermeiden.

Clipping

Clipping ist standardmäßig deaktiviert und sollte nur bei Bedarf aktiviert werden.

Clipping ist ein visueller Effekt, KEINE Optimierung. Es erhöht (statt zu verringern) die Komplexität für den Renderer. Ist das Clipping aktiviert, schneidet ein Element sowohl seine eigene Darstellung als auch die seiner untergeordneten Elemente auf sein Begrenzungsrechteck zu. Dadurch kann der Renderer die Zeichenreihenfolge der Elemente nicht mehr frei neu anordnen, was im besten Fall zu einer suboptimalen Durchquerung des Szenengraphen führt.

Clipping innerhalb eines Delegaten ist besonders ungünstig und sollte unter allen Umständen vermieden werden.

Übermalung und unsichtbare Elemente

Wenn Sie Elemente haben, die vollständig von anderen (undurchsichtigen) Elementen verdeckt werden, sollten Sie deren Eigenschaft „visible“ am besten auf „ false “ setzen, da sie sonst unnötigerweise gezeichnet werden.

Ebenso sollten Elemente, die unsichtbar sind (zum Beispiel die zweite Registerkarte in einem Registerkarten-Widget, während die erste Registerkarte angezeigt wird), aber beim Start initialisiert werden müssen (zum Beispiel, wenn die Instanziierung der zweiten Registerkarte zu lange dauert, um sie erst bei deren Aktivierung durchzuführen), sollten die Eigenschaft „visible“ auf „ false “ gesetzt haben, um den Aufwand für ihre Darstellung zu vermeiden (obwohl, wie zuvor erläutert, weiterhin der Aufwand für eventuelle Animationen oder die Auswertung von Bindungen anfällt, da sie nach wie vor aktiv sind).

Transluzent vs. undurchsichtig

Undurchsichtige Inhalte lassen sich im Allgemeinen wesentlich schneller zeichnen als durchsichtige. Der Grund dafür ist, dass durchsichtige Inhalte gemischt werden müssen und der Renderer undurchsichtige Inhalte potenziell besser optimieren kann.

Ein Bild mit einem einzigen durchscheinenden Pixel wird als vollständig durchscheinend behandelt, auch wenn es größtenteils undurchsichtig ist. Das Gleiche gilt für ein „ BorderImage “ mit transparenten Rändern.

Shader

Der Typ „ ShaderEffect “ ermöglicht es, GLSL-Code mit sehr geringem Overhead inline in eine „ Qt Quick “-Anwendung einzubinden. Es ist jedoch wichtig zu beachten, dass das Fragmentprogramm für jedes Pixel der gerenderten Form ausgeführt werden muss. Bei der Bereitstellung auf Low-End-Hardware und wenn der Shader eine große Anzahl von Pixeln abdeckt, sollte der Fragment-Shader auf wenige Befehle beschränkt werden, um Leistungseinbußen zu vermeiden.

Mit GLSL geschriebene Shader ermöglichen komplexe Transformationen und visuelle Effekte, sollten jedoch mit Bedacht eingesetzt werden. Die Verwendung eines „ ShaderEffectSource “ führt dazu, dass eine Szene zunächst in ein FBO vorgerendert wird, bevor sie gezeichnet werden kann. Dieser zusätzliche Aufwand kann recht hoch sein.

Speicherzuweisung und -freigabe

Die Menge an Speicher, die von einer Anwendung zugewiesen wird, und die Art und Weise, wie dieser Speicher zugewiesen wird, sind sehr wichtige Überlegungen. Abgesehen von den offensichtlichen Bedenken hinsichtlich Speicherengpässen auf Geräten mit begrenzten Speicherressourcen ist die Speicherzuweisung auf dem Heap ein relativ rechenintensiver Vorgang, und bestimmte Zuweisungsstrategien können zu einer erhöhten Fragmentierung der Daten über verschiedene Seiten hinweg führen. JavaScript verwendet einen verwalteten Speicher-Heap, der automatisch einer Garbage Collection unterzogen wird; dies hat einige Vorteile, aber auch einige wichtige Auswirkungen.

Eine in QML geschriebene Anwendung nutzt Speicher sowohl aus dem C++-Heap als auch aus einem automatisch verwalteten JavaScript-Heap. Der Anwendungsentwickler muss sich der Feinheiten beider Heaps bewusst sein, um die Leistung zu maximieren.

Tipps für QML-Anwendungsentwickler

Die in diesem Abschnitt enthaltenen Tipps und Vorschläge dienen lediglich als Richtlinien und sind möglicherweise nicht in allen Fällen anwendbar. Führen Sie unbedingt sorgfältige Benchmarks und Analysen Ihrer Anwendung anhand empirischer Kennzahlen durch, um die bestmöglichen Entscheidungen treffen zu können.

Komponenten verzögert instanziieren und initialisieren

Wenn Ihre Anwendung aus mehreren Ansichten besteht (z. B. mehreren Registerkarten), aber jeweils nur eine davon benötigt wird, können Sie die verzögerte Instanziierung nutzen, um den zu jedem Zeitpunkt benötigten Speicherbedarf zu minimieren. Weitere Informationen finden Sie im vorherigen Abschnitt zur verzögerten Initialisierung.

Nicht verwendete Objekte löschen

Wenn Sie Komponenten verzögert laden oder Objekte während eines JavaScript-Ausdrucks dynamisch erstellen, ist es oft besser, diese manuell zu „ destroy() “ (auflösen), anstatt darauf zu warten, dass die automatische Garbage Collection dies übernimmt. Weitere Informationen finden Sie im vorherigen Abschnitt über die Steuerung der Lebensdauer von Elementen.

Rufen Sie den Garbage Collector nicht manuell auf

In den meisten Fällen ist es nicht ratsam, den Garbage Collector manuell aufzurufen, da dies den GUI-Thread für einen längeren Zeitraum blockiert. Dies kann zu übersprungenen Frames und ruckelnden Animationen führen, was unter allen Umständen vermieden werden sollte.

Es gibt einige Fälle, in denen der manuelle Aufruf des Garbage Collectors akzeptabel ist (dies wird in einem späteren Abschnitt näher erläutert), aber in den meisten Fällen ist der Aufruf des Garbage Collectors unnötig und kontraproduktiv.

Vermeiden Sie die Definition mehrerer identischer impliziter Typen

Wenn für ein QML-Element eine benutzerdefinierte Eigenschaft in QML definiert ist, wird diese zu einem eigenen impliziten Typ. Dies wird in einem späteren Abschnitt näher erläutert. Werden in einem „ Component “ mehrere identische implizite Typen definiert, wird Speicherplatz verschwendet. In diesem Fall ist es in der Regel besser, explizit eine neue Komponente zu definieren, die dann wiederverwendet werden kann. Erwägen Sie in einem solchen Fall die Definition einer Inline-Komponente mithilfe des Schlüsselworts ` component `.

Das Definieren einer benutzerdefinierten Eigenschaft kann oft eine vorteilhafte Leistungsoptimierung darstellen (beispielsweise um die Anzahl der erforderlichen oder neu ausgewerteten Bindungen zu reduzieren) oder die Modularität und Wartbarkeit einer Komponente verbessern. In diesen Fällen wird die Verwendung benutzerdefinierter Eigenschaften empfohlen. Wird der neue Typ jedoch mehr als einmal verwendet, sollte er zur Schonung des Arbeitsspeichers in eine eigene Komponente (inline oder als .qml-Datei) ausgelagert werden.

Vorhandene Komponenten wiederverwenden

Wenn Sie erwägen, eine neue Komponente zu definieren, sollten Sie unbedingt überprüfen, ob eine solche Komponente nicht bereits im Komponentensatz Ihrer Plattform vorhanden ist. Andernfalls zwingen Sie die QML-Engine dazu, Typdaten für einen Typ zu generieren und zu speichern, der im Wesentlichen ein Duplikat einer anderen, bereits vorhandenen und möglicherweise bereits geladenen Komponente ist.

Verwenden Sie Singleton-Typen anstelle von Pragma-Bibliotheksskripten

Wenn Sie ein „pragma library“-Skript verwenden, um anwendungsweite Instanzdaten zu speichern, sollten Sie stattdessen die Verwendung eines Singleton-Typs mit dem Befehl „ QObject “ in Betracht ziehen. Dies sollte zu einer besseren Leistung führen und den Verbrauch von JavaScript-Heap-Speicher reduzieren.

Speicherzuweisung in einer QML-Anwendung

Der Speicherverbrauch einer QML-Anwendung lässt sich in zwei Teile unterteilen: den nativen Heap-Speicherverbrauch und den JavaScript-Heap-Speicherverbrauch. Ein Teil des in jedem Bereich zugewiesenen Speichers ist unvermeidbar, da er von der QML-Engine oder der JavaScript-Engine zugewiesen wird, während der Rest von den Entscheidungen des Anwendungsentwicklers abhängt.

Der native Heap enthält:

  • den festen und unvermeidbaren Overhead der QML-Engine (Implementierungsdatenstrukturen, Kontextinformationen usw.);
  • komponentenbezogene kompilierte Daten und Typinformationen, einschließlich typbezogener Eigenschaftsmetadaten, die von der QML-Engine je nachdem, welche Module und Komponenten von der Anwendung geladen werden, generiert oder aus dem Festplatten-Cache geladen werden;
  • objektbezogene C++-Daten (einschließlich Eigenschaftswerte) sowie eine elementbezogene Metaobjekthierarchie, je nachdem, welche Komponenten die Anwendung instanziiert;
  • alle Daten, die speziell durch QML-Importe (Bibliotheken) zugewiesen werden.

Der JavaScript-Heap enthält:

  • den festen und unvermeidbaren Overhead der JavaScript-Engine selbst (einschließlich integrierter JavaScript-Typen);
  • den festen und unvermeidbaren Overhead unserer JavaScript-Integration (Konstruktorfunktionen für geladene Typen, Funktionsvorlagen usw.);
  • typspezifische Layout-Informationen und andere interne Typdaten, die von der JavaScript-Engine zur Laufzeit für jeden Typ generiert werden (siehe Hinweis unten bezüglich der Typen);
  • objektbezogene JavaScript-Daten („var“-Eigenschaften, JavaScript-Funktionen und Signal-Handler sowie nicht optimierte Bindungsausdrücke);
  • Variablen, die während der Auswertungen von Ausdrücken zugewiesen werden.

Darüber hinaus wird ein JavaScript-Heap für die Verwendung im Hauptthread und optional ein weiterer JavaScript-Heap für die Verwendung im „ WorkerScript “-Thread zugewiesen. Wenn eine Anwendung kein „ WorkerScript “-Element verwendet, entsteht dieser Overhead nicht. Der JavaScript-Heap kann mehrere Megabyte groß sein, weshalb es für Anwendungen, die für Geräte mit begrenztem Speicher entwickelt wurden, möglicherweise am besten ist, das ` WorkerScript `-Element zu vermeiden.

Beachten Sie, dass sowohl die QML-Engine als auch die JavaScript-Engine automatisch eigene Caches mit Typdaten zu den beobachteten Typen generieren. Jede von einer Anwendung geladene Komponente ist ein eigenständiger (expliziter) Typ, und jedes Element (Komponenteninstanz), das in QML eigene benutzerdefinierte Eigenschaften definiert, ist ein impliziter Typ. Jedes Element (Instanz einer Komponente), das keine benutzerdefinierten Eigenschaften definiert, wird von der JavaScript- und der QML-Engine als vom Typ angesehen, der explizit durch die Komponente definiert ist, und nicht als sein eigener impliziter Typ.

Betrachten Sie das folgende Beispiel:

import QtQuick

Item {
    id: root

    Rectangle {
        id: r0
        color: "red"
    }

    Rectangle {
        id: r1
        color: "blue"
        width: 50
    }

    Rectangle {
        id: r2
        property int customProperty: 5
    }

    Rectangle {
        id: r3
        property string customProperty: "hello"
    }

    Rectangle {
        id: r4
        property string customProperty: "hello"
    }
}

Im vorangegangenen Beispiel verfügen die Rechtecke r0 und r1 über keine benutzerdefinierten Eigenschaften, weshalb die JavaScript- und QML-Engines sie beide als vom selben Typ betrachten. Das heißt, r0 und r1 werden beide als vom explizit definierten Typ „ Rectangle “ angesehen. Die Rechtecke r2, r3 und r4 verfügen jeweils über benutzerdefinierte Eigenschaften und werden jeweils als unterschiedliche (implizite) Typen betrachtet. Beachten Sie, dass r3 und r4 jeweils als unterschiedliche Typen angesehen werden, obwohl sie identische Eigenschaftsinformationen aufweisen – einfach deshalb, weil die benutzerdefinierte Eigenschaft in der Komponente, von der sie Instanzen sind, nicht deklariert wurde.

Wären r3 und r4 beide Instanzen einer „ RectangleWithString “-Komponente und würde diese Komponentendefinition die Deklaration einer String-Eigenschaft namens customProperty enthalten, würden r3 und r4 als vom selben Typ angesehen werden (das heißt, sie wären Instanzen des Typs „ RectangleWithString “, anstatt ihren eigenen impliziten Typ zu definieren).

Vertiefende Überlegungen zur Speicherzuweisung

Bei Entscheidungen bezüglich Speicherzuweisung oder Leistungsabwägungen ist es wichtig, die Auswirkungen der CPU-Cache-Leistung, der Auslagerung durch das Betriebssystem und der Garbage Collection der JavaScript-Engine zu berücksichtigen. Mögliche Lösungen sollten sorgfältig im Benchmark getestet werden, um sicherzustellen, dass die beste ausgewählt wird.

Keine noch so umfassenden allgemeinen Richtlinien können ein fundiertes Verständnis der zugrunde liegenden Prinzipien der Informatik ersetzen, das mit praktischen Kenntnissen über die Implementierungsdetails der Plattform einhergeht, für die der Anwendungsentwickler programmiert. Darüber hinaus kann keine noch so umfangreiche theoretische Berechnung eine gute Auswahl an Benchmarks und Analysewerkzeugen ersetzen, wenn es darum geht, Kompromissentscheidungen zu treffen.

Fragmentierung

Fragmentierung ist ein Problem bei der C++-Entwicklung. Wenn der Anwendungsentwickler keine C++-Typen oder Plugins definiert, kann er diesen Abschnitt getrost ignorieren.

Im Laufe der Zeit belegt eine Anwendung große Teile des Speichers, schreibt Daten in diesen Speicher und gibt anschließend Teile davon wieder frei, sobald sie die Nutzung bestimmter Daten beendet hat. Dies kann dazu führen, dass sich „freier“ Speicher in nicht zusammenhängenden Blöcken befindet, die nicht an das Betriebssystem zurückgegeben werden können, damit andere Anwendungen sie nutzen können. Dies wirkt sich auch auf die Caching- und Zugriffseigenschaften der Anwendung aus, da die „aktiven“ Daten über viele verschiedene Seiten des physischen Speichers verteilt sein können. Dies könnte wiederum das Betriebssystem zum Auslagern zwingen, was zu Dateisystem-E/A führen kann – was vergleichsweise gesehen ein extrem langsamer Vorgang ist.

Eine Fragmentierung lässt sich vermeiden, indem Pool-Allokatoren (und andere Allokatoren für zusammenhängenden Speicher) verwendet werden, indem die Menge des jeweils zugewiesenen Speichers durch sorgfältiges Management der Objektlebensdauer reduziert wird, indem Caches regelmäßig bereinigt und neu aufgebaut werden oder indem eine speicherverwaltete Laufzeitumgebung mit Garbage Collection (wie beispielsweise JavaScript) genutzt wird.

Garbage Collection

JavaScript verfügt über eine Garbage Collection. Speicher, der auf dem JavaScript-Heap (im Gegensatz zum nativen Heap) zugewiesen wird, gehört der JavaScript-Engine. Die Engine sammelt in regelmäßigen Abständen alle nicht referenzierten Daten auf dem JavaScript-Heap ein.

Auswirkungen der Garbage Collection

Die Garbage Collection hat Vor- und Nachteile. Sie bedeutet, dass die manuelle Verwaltung der Objektlebensdauer an Bedeutung verliert. Allerdings bedeutet sie auch, dass ein potenziell lang andauernder Vorgang von der JavaScript-Engine zu einem Zeitpunkt ausgelöst werden kann, der außerhalb der Kontrolle des Anwendungsentwicklers liegt. Sofern der Anwendungsentwickler die Nutzung des JavaScript-Heaps nicht sorgfältig berücksichtigt, können sich die Häufigkeit und Dauer der Garbage Collection negativ auf das Anwendungserlebnis auswirken. Seit Qt 6.8 arbeitet der Garbage Collector inkrementell, was bedeutet, dass er zwar kürzere, aber potenziell häufigere Unterbrechungen verursacht.

Manuelles Aufrufen des Garbage Collectors

Eine in QML geschriebene Anwendung wird (höchstwahrscheinlich) zu einem bestimmten Zeitpunkt eine Garbage Collection erfordern. Zwar wird die Garbage Collection automatisch von der JavaScript-Engine nach ihrem eigenen Zeitplan ausgelöst, doch ist es gelegentlich besser, wenn der Anwendungsentwickler selbst entscheidet, wann der Garbage Collector manuell aufgerufen werden soll (obwohl dies in der Regel nicht der Fall ist).

Der Anwendungsentwickler weiß wahrscheinlich am besten, wann eine Anwendung für längere Zeit im Leerlauf sein wird. Wenn eine QML-Anwendung viel JavaScript-Heap-Speicher belegt und dadurch regelmäßige, störende Garbage-Collection-Zyklen während besonders leistungssensitiver Aufgaben (z. B. Scrollen durch Listen, Animationen usw.) verursacht, kann es für den Anwendungsentwickler sinnvoll sein, den Garbage Collector in Phasen ohne Aktivität manuell aufzurufen. Inaktive Phasen eignen sich ideal für die Garbage Collection, da der Benutzer keine Beeinträchtigung der Benutzererfahrung (übersprungene Frames, ruckartige Animationen usw.) bemerkt, die durch den Aufruf des Garbage Collectors während laufender Aktivitäten entstehen würde.

Der Garbage Collector kann manuell aufgerufen werden, indem innerhalb von JavaScript die Funktion ` gc() ` aufgerufen wird. Dies führt zur Durchführung eines vollständigen, nicht inkrementellen Garbage-Collection-Zyklus, dessen Ausführung zwischen einigen hundert und mehr als tausend Millisekunden dauern kann und daher nach Möglichkeit vermieden werden sollte.

Abwägung zwischen Speicherbedarf und Leistung

In manchen Situationen ist es möglich, einen erhöhten Speicherbedarf gegen eine verkürzte Verarbeitungszeit einzutauschen. Beispielsweise führt das Zwischenspeichern des Ergebnisses einer Symbolsuche, die in einer engen Schleife verwendet wird, in einer temporären Variablen in einem JavaScript-Ausdruck zu einer erheblichen Leistungssteigerung bei der Auswertung dieses Ausdrucks, erfordert jedoch die Zuweisung einer temporären Variablen. In manchen Fällen sind solche Kompromisse sinnvoll (wie im obigen Beispiel, das fast immer sinnvoll ist), in anderen Fällen kann es jedoch besser sein, eine etwas längere Verarbeitungszeit in Kauf zu nehmen, um eine Erhöhung der Speicherbelastung des Systems zu vermeiden.

In manchen Fällen können die Auswirkungen einer erhöhten Speicherbelastung extrem sein. Unter bestimmten Umständen kann der Kompromiss zwischen Speicherverbrauch und einem vermeintlichen Leistungsgewinn zu verstärktem Page-Thrash oder Cache-Thrash führen, was einen enormen Leistungsabfall zur Folge hat. Es ist stets notwendig, die Auswirkungen solcher Kompromisse sorgfältig zu benchmarken, um zu ermitteln, welche Lösung in einer bestimmten Situation die beste ist.

Ausführliche Informationen zur Cache-Leistung und zu Kompromissen zwischen Speicher und Zeit finden Sie in den folgenden Artikeln:

Schneller Systemstart und Startoptimierung

Basierend auf praktischen Erfahrungen bei der Optimierung von „ Qt Quick “-Anwendungen für einen schnellen Systemstart sollten Sie die folgenden bewährten Vorgehensweisen berücksichtigen:

  • Entwerfen Sie Ihre Anwendung von Anfang an so, dass sie schnell startet. Überlegen Sie, was der Benutzer als Erstes sehen soll.
  • Nutzen Sie die QML Profiler , um Engpässe beim Start zu identifizieren.
  • Nutzen Sie das Kettenladen. Führen Sie nur so viele „ loaders “ aus, wie Ihre CPU Kerne hat (z. B. bei zwei Kernen: zwei gleichzeitig laufende Loader).
  • Der erste „ loader “ sollte nicht asynchron sein, damit bereits sofort Inhalte angezeigt werden. Lösen Sie die asynchronen Loader erst danach aus.
  • Stellen Sie eine Verbindung zu Backend-Diensten erst bei Bedarf her.
  • Erstellen Sie QML-Module, die bei Bedarf importiert werden. Durch die Verwendung von verzögert geladenen Modulen und Typen können Sie nicht kritische Dienste Ihrer Anwendung bedarfsgerecht zur Verfügung stellen.
  • Optimieren Sie Ihre PNG-/JPG-Bilder mit Tools wie optipng.
  • Optimieren Sie Ihre 3D-Modelle, indem Sie die Anzahl der Scheitelpunkte reduzieren und nicht sichtbare Teile entfernen.
  • Optimieren Sie das Laden von 3D-Modellen durch die Verwendung von glTF.
  • Beschränken Sie die Verwendung von „clip“ und „opacity“, da diese die Leistung beeinträchtigen können.
  • Ermitteln Sie die GPU-Einschränkungen und berücksichtigen Sie diese bei der Gestaltung der Benutzeroberfläche. Weitere Informationen finden Sie unter Frame Captures and Performance Profiling.
  • Verwenden Sie den „Qt Quick Compiler“, um die QML-Dateien vorab zu kompilieren.
  • Prüfen Sie, ob für Ihre Architektur eine statische Verknüpfung möglich ist.
  • Streben Sie deklarative Bindungen anstelle von imperativen Signal-Handlern an.
  • Halten Sie Eigenschaftsbindungen einfach. Halten Sie QML-Code generell einfach, unterhaltsam und lesbar. Eine gute Leistung ergibt sich daraus von selbst.
  • Ersetzen Sie komplexe Steuerelemente durch Bilder oder Shader, wenn die Erstellungszeit ein Problem darstellt.

Nicht tun:

  • Es mit QML übertreiben. Selbst wenn Sie QML verwenden, müssen Sie nicht absolut alles in QML erledigen.
  • Initialisieren Sie nicht alles in Ihrer main.cpp.
  • Erstellen Sie keine großen Singletons, die alle erforderlichen Schnittstellen enthalten.
  • Erstellen Sie keine komplexen Delegates für „ ListView “ oder andere Ansichten.
  • Verwende „clip“ nur, wenn es unbedingt notwendig ist.
  • Tappen Sie in die häufige Falle, Loader übermäßig zu nutzen. „ Loader “ eignet sich hervorragend für das verzögerte Laden größerer Elemente wie Anwendungsseiten, verursacht jedoch zu viel Overhead beim Laden einfacher Elemente. Es ist keine Zauberei, die alles und jedes beschleunigt. Es handelt sich um ein zusätzliches Element mit einem zusätzlichen QML-Kontext.

Diese Vorgehensweisen tragen dazu bei, Startzeiten im Subsekundenbereich und eine flüssige Benutzererfahrung zu erreichen, insbesondere auf eingebetteten Geräten.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.