QML-Leistungsüberlegungen und -Vorschläge
Überlegungen zum Timing
Als Anwendungsentwickler müssen Sie dafür sorgen, dass die Rendering-Engine eine konstante Bildwiederholrate von 60 Bildern pro Sekunde erreicht. 60 FPS bedeutet, dass zwischen jedem Bild etwa 16 Millisekunden liegen, in denen die Verarbeitung stattfinden kann, einschließlich der Verarbeitung, die zum Hochladen der Zeichenprimitive in die Grafikhardware erforderlich ist.
In der Praxis bedeutet dies, dass der Anwendungsentwickler:
- wann immer möglich asynchrone, ereignisgesteuerte Programmierung verwenden
- Worker-Threads für wichtige Verarbeitungsvorgänge verwenden
- die Ereignisschleife niemals manuell drehen
- nie mehr als ein paar Millisekunden pro Frame in blockierenden Funktionen verbringen
Andernfalls kommt es zu übersprungenen Frames, was sich drastisch auf das Benutzererlebnis auswirkt.
Hinweis: Ein verlockendes Muster, das jedoch niemals verwendet werden sollte, ist die Erstellung eines eigenen QEventLoop oder der Aufruf von QCoreApplication::processEvents(), um das Blockieren innerhalb eines von QML aufgerufenen C++-Codeblocks zu vermeiden. Dies ist gefährlich, denn wenn eine Ereignisschleife in einem Signalhandler oder einer Bindung eingegeben wird, fährt die QML-Engine fort, andere Bindungen, Animationen, Übergänge usw. auszuführen. Diese Bindungen können dann Seiteneffekte verursachen, die z. B. die Hierarchie, die Ihre Ereignisschleife enthält, zerstören.
Profiling
Der wichtigste Tipp ist: Verwenden Sie den QML Profiler, der in Qt Creator enthalten ist. Wenn Sie wissen, wie viel Zeit in einer Anwendung verbracht wird, können Sie sich auf tatsächlich vorhandene Problembereiche konzentrieren, anstatt auf solche, die potenziell vorhanden sind. Weitere Informationen zur Verwendung des QML Profilers finden Sie im HandbuchQt Creator .
Wenn Sie feststellen, 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, den Code ohne Profiling zu optimieren, wird wahrscheinlich eher zu sehr geringen als zu erheblichen Leistungsverbesserungen führen.
JavaScript-Code
Die meisten QML-Anwendungen enthalten eine große Menge an JavaScript-Code in Form von dynamischen Funktionen, Signal-Handlern und Property-Binding-Ausdrücken. Dies ist im Allgemeinen kein Problem. Dank einiger Optimierungen in der QML-Engine, z. B. im Bindungscompiler, kann dies (in einigen Anwendungsfällen) schneller sein als der Aufruf einer C++-Funktion. Es muss jedoch darauf geachtet werden, dass nicht versehentlich eine unnötige Verarbeitung ausgelöst wird.
Typkonvertierung
Ein wesentlicher Nachteil der Verwendung von JavaScript besteht darin, dass in den meisten 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 recht kostengünstig, aber in anderen Fällen kann es recht teuer werden. Ein Beispiel dafür ist die Zuweisung einer C++ QVariantMap Q_PROPERTY zu einer QML "variant" Eigenschaft. Auch Listen können teuer sein, obwohl Sequenzen bestimmter Typen (QList von int, qreal, bool, QString und QUrl) kostengünstig sein sollten; andere Listentypen sind mit teuren Konvertierungskosten verbunden (Erstellen eines neuen JavaScript-Arrays und Hinzufügen neuer Typen, einer nach dem anderen, mit einer Konvertierung pro Typ von C++-Typinstanz zu JavaScript-Wert).
Die Konvertierung zwischen einigen grundlegenden Eigenschaftstypen (z. B. "string"- und "url"-Eigenschaften) kann ebenfalls teuer sein. Durch die Verwendung des am besten passenden Eigenschaftstyps wird eine unnötige Konvertierung vermieden.
Wenn Sie eine QVariantMap für QML freigeben müssen, verwenden Sie eine "var"-Eigenschaft und keine "variant"-Eigenschaft. Im Allgemeinen sollte "property var" für jeden Anwendungsfall ab QtQuick 2.0 als besser angesehen werden als "property variant" (beachten Sie, dass "property variant" als veraltet markiert ist), da es die Speicherung einer echten JavaScript-Referenz ermöglicht (was die Anzahl der in bestimmten Ausdrücken erforderlichen Konvertierungen reduzieren kann).
Auflösen von Eigenschaften
Die Auflösung von Eigenschaften braucht Zeit. In einigen Fällen kann das Ergebnis einer Suche zwar zwischengespeichert und wiederverwendet werden, doch ist es immer am besten, unnötige Arbeit nach Möglichkeit ganz zu vermeiden.
Im folgenden Beispiel haben wir einen Codeblock, der häufig ausgeführt wird (in diesem Fall ist es der Inhalt einer expliziten Schleife, aber es könnte z. B. auch ein häufig ausgewerteter Bindungsausdruck sein) und in dem wir das Objekt mit der id "rect" und seiner Eigenschaft "color" mehrfach auflösen:
// bad.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which, value) { 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"); } }
Wir könnten stattdessen die gemeinsame Basis nur einmal in dem Block auflösen:
// good.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which, value) { 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"); } }
Allein diese einfache Änderung führt zu einer erheblichen Leistungsverbesserung. Beachten Sie, dass der obige Code noch weiter verbessert werden kann (da sich die nachgeschlagene Eigenschaft während der Schleifenverarbeitung nie ändert), indem die Auflösung der Eigenschaft wie folgt aus der Schleife herausgenommen wird:
// better.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which, value) { 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 Ausdruck für eine Eigenschaftsbindung wird neu ausgewertet, wenn eine der Eigenschaften, auf die er verweist, geändert wird. Aus diesem Grund sollten Bindungsausdrücke so einfach wie möglich gehalten werden.
Wenn Sie eine Schleife haben, in der Sie einige Verarbeitungen vornehmen, 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 inkrementell zu aktualisieren, um zu vermeiden, dass die Bindungsausdrücke während der Zwischenphasen der Akkumulation neu bewertet 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 führt dazu, dass die Eigenschaftsbindung "text" sechsmal neu bewertet wird (was dazu führt, dass alle anderen Eigenschaftsbindungen, die sich auf den Textwert stützen, sowie der onTextChanged-Signalhandler jedes Mal neu bewertet werden und den Text jedes Mal für die Anzeige ausgeben). Dies ist in diesem Fall eindeutig unnötig, da wir uns wirklich nur für den Endwert der Akkumulation interessieren.
Es 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; } }
Sequenz-Tipps
Wie bereits erwähnt, sind einige Sequenztypen schnell (zum Beispiel QList<int>, QList<qreal>, QList<bool>, QList<QString>, QStringList und QList<QUrl>), während andere viel langsamer sind. Abgesehen davon, dass diese Typen, wo immer möglich, anstelle von langsameren Typen verwendet werden, gibt es einige andere leistungsbezogene Semantiken, die Sie beachten müssen, um die beste Leistung zu erzielen.
Erstens gibt es zwei verschiedene Implementierungen für Sequenztypen: eine für den Fall, dass die Sequenz eine Q_PROPERTY einer QObject ist (wir nennen dies eine Referenzsequenz), und eine andere für den Fall, dass die Sequenz von einer Q_INVOKABLE Funktion einer QObject zurückgegeben wird (wir nennen dies eine Kopiersequenz).
Eine Referenzsequenz wird über QMetaObject::property() gelesen und geschrieben und somit als QVariant gelesen und geschrieben. Das bedeutet, dass das Ändern des Wertes eines beliebigen Elements in der Sequenz von JavaScript aus drei Schritte nach sich zieht: Die gesamte Sequenz wird von QObject gelesen (als QVariant, aber dann in eine Sequenz des richtigen Typs umgewandelt); das Element am angegebenen Index wird in dieser Sequenz geändert; und die gesamte Sequenz wird zurück in QObject geschrieben (als QVariant).
Eine Kopiersequenz ist wesentlich einfacher, da die eigentliche Sequenz in den Ressourcendaten des JavaScript-Objekts gespeichert ist, so dass kein Lese-Änderungs-Schreib-Zyklus stattfindet (stattdessen werden die Ressourcendaten direkt geändert).
Daher sind Schreibvorgänge auf Elemente einer Referenzsequenz viel langsamer als Schreibvorgänge auf Elemente einer Kopiersequenz. Tatsächlich ist das Schreiben in ein einzelnes Element einer N-Element-Referenzsequenz von den Kosten her gleichbedeutend mit der Zuweisung einer N-Element-Kopiersequenz zu dieser Referenzsequenz, so dass es in der Regel besser ist, eine temporäre Kopiersequenz zu modifizieren und das Ergebnis dann während der Berechnung einer Referenzsequenz zuzuweisen.
Nehmen wir an, dass der folgende C++-Typ existiert (und zuvor im Namensraum "Qt.example" registriert wurde):
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 einer schlechten 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"); } }
Die QObject Eigenschaft Lesen und Schreiben in der inneren Schleife, verursacht durch den "qrealListProperty[j] = j"
Ausdruck, macht diesen Code sehr suboptimal. Stattdessen wäre etwas funktional Äquivalentes, aber viel 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"); } }
Zweitens wird ein Änderungssignal für die Eigenschaft ausgegeben, wenn sich ein Element darin ändert. Wenn Sie viele Bindungen an ein bestimmtes Element in einer Sequenzeigenschaft haben, ist es besser, eine dynamische Eigenschaft zu erstellen, die an dieses Element gebunden ist, und diese dynamische Eigenschaft als Symbol in den Bindungsausdrücken anstelle des Sequenzelements zu verwenden, da sie nur dann eine Neuauswertung der Bindungen verursacht, wenn sich ihr Wert ändert.
Dies ist ein ungewöhnlicher Anwendungsfall, auf den die meisten Kunden nie stoßen sollten, aber es lohnt sich, ihn zu kennen, falls Sie so etwas tun:
// 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 nur das Element bei Index 2 in der Schleife geändert wird, die drei Bindungen alle neu bewertet werden, da die Granularität des Änderungssignals darin besteht, 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 nur die Zwischenbindung jedes Mal neu ausgewertet, was zu einer erheblichen Leistungssteigerung führt.
Tipps zum Wert-Typ
Eigenschaften vom Typ "Wert" (Schrift, Farbe, vector3d usw.) haben eine ähnliche QObject Eigenschaft und eine andere Benachrichtigungssemantik als Eigenschaften vom Typ "Sequenz". Daher sind die oben für Sequenzen gegebenen Tipps auch für wertartige Eigenschaften anwendbar. Während sie bei Wertetypen in der Regel weniger problematisch sind (da die Anzahl der Untereigenschaften eines Wertetyps in der Regel weitaus geringer ist als die Anzahl der Elemente in einer Sequenz), wirkt sich jede Zunahme der Anzahl von Bindungen, die unnötigerweise neu ausgewertet werden, negativ auf die Leistung aus.
Allgemeine Leistungstipps
Allgemeine JavaScript-Leistungsüberlegungen, die sich aus dem Sprachdesign ergeben, gelten auch für QML. Besonders hervorzuheben:
- Vermeiden Sie nach Möglichkeit die Verwendung von eval()
- Löschen Sie keine Eigenschaften von Objekten
Gemeinsame Elemente der Schnittstelle
Text-Elemente
Die Berechnung von Textlayouts kann ein langsamer Vorgang 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 (weil Sie Bilder einbetten oder Tags verwenden müssen, um Zeichenbereiche für bestimmte Formatierungen (fett, kursiv usw.) und nicht den gesamten Text anzugeben), sollten Sie StyledText
verwenden.
Sie sollten AutoText
nur verwenden, wenn der Text StyledText
sein könnte (aber wahrscheinlich nicht ist), da dieser Modus Parsing-Kosten verursacht. Der Modus RichText
sollte nicht verwendet werden, da StyledText
fast alle seine Funktionen zu einem Bruchteil seiner Kosten bietet.
Bilder
Bilder sind ein wesentlicher Bestandteil jeder Benutzeroberfläche. Leider sind sie aber auch eine große Problemquelle, da sie viel Zeit zum Laden benötigen, viel Speicherplatz verbrauchen und auf unterschiedliche Weise verwendet werden.
Asynchrones Laden
Bilder sind oft recht groß, und es ist daher ratsam, dafür zu sorgen, dass das Laden eines Bildes den UI-Thread nicht blockiert. Setzen Sie die Eigenschaft "asynchron" des QML-Image-Elements auf true
, um das asynchrone Laden von Bildern aus dem lokalen Dateisystem zu ermöglichen (entfernte Bilder werden immer asynchron geladen), sofern dies keine negativen Auswirkungen auf die Ästhetik der Benutzeroberfläche hat.
Bei Bildelementen mit der Eigenschaft "asynchron" auf true
werden Bilder in einem Worker-Thread mit niedriger Priorität geladen.
Explizite Quellgröße
Wenn Ihre Anwendung ein großes Bild lädt, es aber in einem kleinen Element anzeigt, setzen Sie die Eigenschaft "sourceSize" auf die Größe des gerenderten Elements, um sicherzustellen, dass die kleiner skalierte Version des Bildes im Speicher bleibt und nicht die große.
Beachten Sie, dass eine Änderung der "sourceSize"-Eigenschaft dazu führt, dass das Bild neu geladen werden muss.
Laufzeitkomposition vermeiden
Denken Sie auch daran, dass Sie die Kompositionsarbeit zur Laufzeit vermeiden können, indem Sie die vorkomponierte Bildressource mit Ihrer Anwendung bereitstellen (z. B. indem Sie Elemente mit Schatteneffekten bereitstellen).
Glättung von Bildern vermeiden
Aktivieren Sie image.smooth
nur bei Bedarf. Sie ist auf manchen Geräten langsamer und hat keinen visuellen Effekt, wenn das Bild in seiner natürlichen Größe angezeigt wird.
Malen
Vermeiden Sie es, denselben Bereich mehrmals zu malen. Verwenden Sie Item als Wurzelelement und nicht Rectangle, um zu vermeiden, dass der Hintergrund mehrmals gezeichnet wird.
Elemente mit Ankern 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 wird effizienter mit Ankern erreicht:
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 x-, y-, Breiten- und Höheneigenschaften von visuellen Objekten, statt durch die Verwendung von Ankern) ist relativ langsam, ermöglicht aber maximale Flexibilität.
Wenn das Layout nicht dynamisch ist, ist die statische Initialisierung der x-, y-, Breiten- und Höheneigenschaften der performanteste Weg, das Layout festzulegen. Elementkoordinaten sind immer relativ zu ihrem Elternteil, wenn Sie also einen festen Abstand zur 0,0-Koordinate des Elternteils haben möchten, sollten Sie keine Anker verwenden. Im folgenden Beispiel befinden sich die untergeordneten Rechteckobjekte an der gleichen Stelle, aber der gezeigte Ankercode ist nicht so ressourceneffizient wie der Code, der eine feste Positionierung über 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 haben mindestens ein Modell, das Daten an eine Ansicht weiterleitet. Es gibt einige semantische Aspekte, die Anwendungsentwickler beachten müssen, um eine maximale Leistung zu erzielen.
Benutzerdefinierte C++-Modelle
Oft ist es wünschenswert, ein eigenes benutzerdefiniertes Modell in C++ für die Verwendung mit einer Ansicht in QML zu schreiben. Während die optimale Implementierung eines solchen Modells stark vom Anwendungsfall abhängt, den es erfüllen muss, sind einige allgemeine Richtlinien wie folgt:
- Seien Sie so asynchron wie möglich
- Führen Sie die gesamte Verarbeitung in einem Worker-Thread (mit niedriger Priorität) durch.
- Stapelverarbeitung von Backend-Operationen, um (potenziell langsame) E/A und IPC zu minimieren
Es ist wichtig zu beachten, dass die Verwendung eines Worker-Threads mit niedriger Priorität empfohlen wird, um das Risiko zu minimieren, den GUI-Thread auszuhungern (was zu einer schlechteren wahrgenommenen Leistung führen könnte). Denken Sie auch daran, dass Synchronisierungs- und Sperrmechanismen eine wesentliche Ursache für eine langsame Leistung sein können, so dass unnötiges Sperren vermieden werden sollte.
ListModel QML-Typ
QML bietet einen ListModel Typ, der verwendet werden kann, um Daten an ListView zu übergeben. Er sollte für die meisten Anwendungsfälle ausreichen und relativ leistungsfähig sein, solange er korrekt verwendet wird.
Auffüllen innerhalb eines Worker-Threads
ListModel Elemente können in einem (niedrig priorisierten) Worker-Thread in JavaScript aufgefüllt werden. Der Entwickler muss explizit "sync()" auf ListModel aus dem WorkerScript aufrufen, damit die Änderungen mit dem Haupt-Thread synchronisiert werden. Weitere Informationen finden Sie in der Dokumentation WorkerScript.
Bitte beachten Sie, dass die Verwendung eines WorkerScript Elements dazu führt, dass eine separate JavaScript-Engine erstellt wird (da die JavaScript-Engine pro Thread arbeitet). Dies führt zu einer erhöhten Speichernutzung. Mehrere WorkerScript Elemente verwenden jedoch alle denselben Worker-Thread, so dass die Speicherauswirkungen der Verwendung eines zweiten oder dritten WorkerScript Elements vernachlässigbar sind, wenn eine Anwendung bereits eines verwendet.
Verwenden Sie keine dynamischen Rollen
Das Element ListModel in QtQuick 2 ist viel leistungsfähiger als in QtQuick 1. Die Leistungsverbesserungen ergeben sich hauptsächlich aus den Annahmen über den Typ der Rollen innerhalb jedes Elements in einem bestimmten Modell - wenn sich der Typ nicht ändert, verbessert sich die Caching-Leistung dramatisch. Wenn sich der Typ dynamisch von Element zu Element ändern kann, wird diese Optimierung unmöglich, und die Leistung des Modells wird um eine Größenordnung schlechter sein.
Daher ist die dynamische Typisierung standardmäßig deaktiviert; der Entwickler muss die boolesche Eigenschaft "dynamicRoles" des Modells speziell einstellen, um die dynamische Typisierung zu aktivieren (und die damit verbundene Leistungsverschlechterung in Kauf nehmen). Wir empfehlen Ihnen, die dynamische Typisierung nicht zu verwenden, wenn es möglich ist, Ihre Anwendung so umzugestalten, dass sie vermieden wird.
Ansichten
View Delegates sollten so einfach wie möglich gehalten werden. Der Delegat sollte gerade so viel QML enthalten, dass die notwendigen Informationen angezeigt werden. Jede zusätzliche Funktionalität, die nicht sofort benötigt wird (z.B. wenn sie mehr Informationen anzeigt, wenn sie angeklickt wird), sollte nicht erstellt werden, bis sie benötigt wird (siehe den kommenden Abschnitt über träge Initialisierung).
Die folgende Liste ist eine gute Zusammenfassung der Dinge, die beim Entwurf eines Delegaten zu beachten sind:
- Je weniger Elemente in einem Delegaten sind, desto schneller können sie erstellt werden, und desto schneller kann die Ansicht gescrollt werden.
- Beschränken Sie die Anzahl der Bindungen in einem Delegaten auf ein Minimum; verwenden Sie insbesondere Anker anstelle von Bindungen für die relative Positionierung innerhalb eines Delegaten.
- Vermeiden Sie die Verwendung von ShaderEffect Elementen innerhalb von Delegaten.
- Aktivieren Sie niemals Clipping auf einem Delegaten.
Sie können die cacheBuffer
Eigenschaft einer Ansicht festlegen, um die asynchrone Erstellung und Pufferung von Delegaten außerhalb des sichtbaren Bereichs zu ermöglichen. Die Verwendung von cacheBuffer
wird für Ansichtsdelegate empfohlen, die nicht trivial sind und wahrscheinlich nicht innerhalb eines einzelnen Frames erstellt werden.
Beachten Sie, dass ein cacheBuffer
zusätzliche Delegierte im Speicher hält. Daher muss der Wert, der sich aus der Verwendung von cacheBuffer
ergibt, gegen den zusätzlichen Speicherverbrauch abgewogen werden. Entwickler sollten Benchmarking verwenden, um den besten Wert für ihren Anwendungsfall zu finden, da der erhöhte Speicherdruck, der durch die Verwendung von cacheBuffer
verursacht wird, in einigen seltenen Fällen zu einer verringerten Bildrate beim Scrollen führen kann.
Visuelle Effekte
Qt Quick 2 enthält mehrere Funktionen, die es Entwicklern und Designern ermöglichen, außergewöhnlich ansprechende Benutzeroberflächen zu erstellen. Fließende und dynamische Übergänge sowie visuelle Effekte können in einer Anwendung sehr wirkungsvoll eingesetzt werden, aber bei der Verwendung einiger Funktionen in QML ist Vorsicht geboten, da sie 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. Normalerweise ist dies erwünscht, aber in anderen Fällen kann es besser sein, die Bindung vor der Durchführung der Animation zu deaktivieren und dann die Bindung nach Abschluss der Animation neu zuzuweisen.
Vermeiden Sie die Ausführung von JavaScript während der Animation. Zum Beispiel sollte die Ausführung eines komplexen JavaScript-Ausdrucks für jedes Bild einer x-Eigenschaftsanimation vermieden werden.
Entwickler sollten besonders vorsichtig sein, wenn sie Skript-Animationen verwenden, da diese im Haupt-Thread ausgeführt werden (und daher dazu führen können, dass Frames übersprungen werden, wenn sie zu lange brauchen, um abgeschlossen zu werden).
Partikel
Das Qt Quick Particles Modul erlaubt es, schöne Partikeleffekte nahtlos in Benutzeroberflächen zu integrieren. Jede Plattform hat jedoch unterschiedliche Grafikhardware-Fähigkeiten, und das Partikelmodul kann die Parameter nicht auf das beschränken, was Ihre Hardware sinnvollerweise unterstützen kann. Je mehr Partikel Sie zu rendern versuchen (und je größer sie sind), desto schneller muss Ihre Grafikhardware sein, um mit 60 FPS zu rendern. Je mehr Partikel gerendert werden, desto schneller muss die CPU sein. Es ist daher wichtig, alle Partikeleffekte auf Ihrer Zielplattform sorgfältig zu testen, um die Anzahl und Größe der Partikel zu kalibrieren, die Sie bei 60 FPS rendern können.
Es ist zu beachten, dass ein Partikelsystem deaktiviert werden kann, wenn es nicht verwendet wird (z. B. bei einem nicht sichtbaren Element), um unnötige Simulationen zu vermeiden.
Ausführlichere Informationen finden Sie in der Anleitung zur Partikelsystemleistung.
Kontrolle der Element-Lebensdauer
Durch die Partitionierung einer Anwendung in einfache, modulare Komponenten, die jeweils in einer einzigen QML-Datei enthalten sind, können Sie die Startzeit der Anwendung verkürzen, die Speichernutzung besser kontrollieren und die Anzahl der aktiven, aber unsichtbaren Elemente in Ihrer Anwendung reduzieren.
Faule Initialisierung
Die QML-Engine hat einige Tricks auf Lager, um sicherzustellen, dass beim Laden und Initialisieren von Komponenten keine Frames übersprungen werden. Es gibt jedoch keinen besseren Weg, um die Startzeit zu verkürzen, als die Arbeit zu vermeiden, die nicht notwendig ist, und sie aufzuschieben, bis sie notwendig ist. Dies kann entweder durch die Verwendung von Loader oder durch die dynamische Erstellung von Komponenten erreicht werden.
Loader verwenden
Der Loader ist ein Element, das das dynamische Laden und Entladen von Komponenten ermöglicht.
- Mit Hilfe der "active"-Eigenschaft eines Loaders kann die Initialisierung bis zum Bedarf verzögert werden.
- Mit der überladenen Version der Funktion "setSource()" können erste Eigenschaftswerte geliefert werden.
- Das Setzen der Eigenschaft "Loader asynchronous " auf "true" kann auch den Ablauf während der Instanziierung einer Komponente verbessern.
Dynamische Erstellung verwenden
Entwickler können die Funktion Qt.createComponent() verwenden, um eine Komponente dynamisch zur Laufzeit aus JavaScript heraus zu erstellen, und dann createObject() aufrufen, um sie zu instanziieren. Abhängig von der im Aufruf spezifizierten Ownership-Semantik muss der Entwickler das erstellte Objekt möglicherweise manuell löschen. Weitere Informationen finden Sie unter Dynamische QML-Objekterstellung aus JavaScript.
Unbenutzte Elemente zerstören
Elemente, die unsichtbar sind, weil sie einem nicht sichtbaren Element untergeordnet sind (z. B. die zweite Registerkarte in einem Registerkarten-Widget, während die erste Registerkarte angezeigt wird), sollten in den meisten Fällen verzögert initialisiert und gelöscht werden, wenn sie nicht mehr verwendet werden, um die laufenden Kosten zu vermeiden, die entstehen, wenn sie aktiv bleiben (z. B. Rendering, Animationen, Auswertung der Eigenschaftsbindung usw.).
Ein Element, das mit einem Loader-Element geladen wurde, kann durch Zurücksetzen der "source"- oder "sourceComponent"-Eigenschaft des Loaders freigegeben werden, während andere Elemente explizit durch den Aufruf von destroy() freigegeben werden können. In einigen Fällen kann es notwendig sein, das Element aktiv zu lassen. In diesem Fall sollte es zumindest unsichtbar gemacht werden.
Weitere Informationen über aktive, aber unsichtbare Elemente finden Sie im nächsten Abschnitt über Rendering.
Rendering
Der Szenegraph, der für das Rendering in QtQuick 2 verwendet wird, erlaubt es, hochdynamische, animierte Benutzeroberflächen flüssig mit 60 FPS zu rendern. Es gibt jedoch einige Dinge, die die Rendering-Leistung drastisch verringern 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 die Komplexität für den Renderer (anstatt sie zu reduzieren). Wenn das Ausschneiden aktiviert ist, schneidet ein Element sein eigenes Bild sowie die Bilder seiner Kinder auf sein Begrenzungsrechteck. Dadurch kann der Renderer die Zeichenreihenfolge der Elemente nicht mehr frei anordnen, was zu einer suboptimalen Traversierung des Szenegraphen im besten Fall führt.
Clipping innerhalb eines Delegaten ist besonders schlecht und sollte unter allen Umständen vermieden werden.
Überzeichnung und unsichtbare Elemente
Wenn Sie Elemente haben, die vollständig von anderen (undurchsichtigen) Elementen verdeckt werden, ist es am besten, ihre "visible"-Eigenschaft auf false
zu setzen, da sie sonst unnötig gezeichnet werden.
In ähnlicher Weise sollten Elemente, die unsichtbar sind (z. B. die zweite Registerkarte in einem Registerkarten-Widget, während die erste Registerkarte angezeigt wird), aber zur Startzeit initialisiert werden müssen (z. B. wenn die Kosten für die Instanziierung der zweiten Registerkarte zu lange dauern, um dies erst bei der Aktivierung der Registerkarte zu tun), ihre "visible"-Eigenschaft auf false
setzen, um die Kosten für das Zeichnen dieser Elemente zu vermeiden (obwohl sie, wie bereits erläutert, immer noch die Kosten für die Auswertung von Animationen oder Bindungen verursachen, da sie noch aktiv sind).
Transluzent vs. Opak
Opake Inhalte sind im Allgemeinen viel schneller zu zeichnen als transluzente. Der Grund dafür ist, dass lichtdurchlässige Inhalte überblendet werden müssen und dass der Renderer undurchsichtige Inhalte möglicherweise besser optimieren kann.
Ein Bild mit einem lichtdurchlässigen Pixel wird als vollständig lichtdurchlässig behandelt, auch wenn es größtenteils undurchsichtig ist. Dasselbe gilt für ein BorderImage mit transparenten Rändern.
Shader
Der Typ ShaderEffect ermöglicht es, GLSL-Code mit sehr geringem Overhead in eine Qt Quick Anwendung einzubinden. Es ist jedoch wichtig zu wissen, dass das Fragment-Programm für jedes Pixel in der gerenderten Form ausgeführt werden muss. Beim Einsatz von Low-End-Hardware und wenn der Shader eine große Anzahl von Pixeln abdeckt, sollte man den Fragment-Shader auf wenige Anweisungen beschränken, um eine schlechte Leistung zu vermeiden.
In GLSL geschriebene Shader ermöglichen das Schreiben komplexer Transformationen und visueller Effekte, sie sollten jedoch mit Vorsicht verwendet werden. Die Verwendung von ShaderEffectSource führt dazu, dass eine Szene in ein FBO vorgerendert werden muss, bevor sie gezeichnet werden kann. Dieser zusätzliche Overhead kann ziemlich teuer sein.
Speicherzuweisung und -sammlung
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 über Out-of-Memory-Bedingungen auf speicherbeschränkten Geräten ist die Zuweisung von Speicher auf dem Heap ein ziemlich rechenintensiver Vorgang, und bestimmte Zuweisungsstrategien können zu einer verstärkten Fragmentierung von Daten über Seiten hinweg führen. JavaScript verwendet einen verwalteten Speicher-Heap, der automatisch gelöscht wird, was einige Vorteile, aber auch einige wichtige Auswirkungen hat.
Eine in QML geschriebene Anwendung verwendet Speicher sowohl aus dem C++-Heap als auch aus einem automatisch verwalteten JavaScript-Heap. Der Anwendungsentwickler muss sich der Feinheiten beider Systeme bewusst sein, um die Leistung zu maximieren.
Tipps für QML-Anwendungsentwickler
Die in diesem Abschnitt enthaltenen Tipps und Vorschläge sind lediglich Richtlinien, die nicht unter allen Umständen anwendbar sind. Stellen Sie sicher, dass Sie Ihre Anwendung sorgfältig anhand empirischer Metriken bewerten und analysieren, um die bestmöglichen Entscheidungen treffen zu können.
Instanziieren und initialisieren Sie Komponenten nach dem Zufallsprinzip
Wenn Ihre Anwendung aus mehreren Ansichten besteht (z. B. mehrere Registerkarten), aber nur eine zu einem bestimmten Zeitpunkt benötigt wird, können Sie die faule Instanziierung verwenden, um die Menge an Speicher, die Sie zu einem bestimmten Zeitpunkt zuweisen müssen, zu minimieren. Weitere Informationen finden Sie im vorherigen Abschnitt über Lazy Initialization.
Unbenutzte Objekte zerstören
Wenn Sie Komponenten "lazy" laden oder Objekte dynamisch während eines JavaScript-Ausdrucks erstellen, ist es oft besser, sie manuell unter destroy()
zu löschen, als auf die automatische Garbage Collection zu warten. Weitere Informationen finden Sie im vorherigen Abschnitt über die Steuerung der Elementlebensdauer.
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 blockieren würde. Dies kann zu übersprungenen Frames und ruckeligen Animationen führen, was um jeden Preis vermieden werden sollte.
Es gibt einige Fälle, in denen das manuelle Aufrufen des Garbage Collectors akzeptabel ist (dies wird in einem späteren Abschnitt näher erläutert), aber in den meisten Fällen ist das Aufrufen des Garbage Collectors unnötig und kontraproduktiv.
Vermeiden Sie die Definition mehrerer identischer impliziter Typen
Wenn ein QML-Element eine in QML definierte benutzerdefinierte Eigenschaft hat, wird diese zu einem eigenen impliziten Typ. Dies wird in einem der nächsten Abschnitte näher erläutert. Wenn mehrere identische implizite Typen inline in einer Komponente definiert werden, wird etwas Speicher verschwendet. In diesem Fall ist es normalerweise besser, explizit eine neue Komponente zu definieren, die dann wiederverwendet werden kann.
Die Definition einer benutzerdefinierten Eigenschaft kann oft eine vorteilhafte Leistungsoptimierung sein (z. B. um die Anzahl der erforderlichen oder neu zu bewertenden Bindungen zu verringern), oder sie kann die Modularität und Wartbarkeit einer Komponente verbessern. In diesen Fällen wird die Verwendung benutzerdefinierter Eigenschaften empfohlen. Allerdings sollte der neue Typ, wenn er mehr als einmal verwendet wird, in eine eigene Komponente (.qml-Datei) aufgeteilt werden, um Speicherplatz zu sparen.
Wiederverwendung bestehender Komponenten
Wenn Sie erwägen, eine neue Komponente zu definieren, sollten Sie sich vergewissern, dass eine solche Komponente nicht bereits im Komponentensatz für Ihre Plattform vorhanden ist. Andernfalls zwingen Sie die QML-Engine, Typdaten für einen Typ zu generieren und zu speichern, der im Wesentlichen ein Duplikat einer anderen, bereits existierenden und möglicherweise bereits geladenen Komponente ist.
Verwenden Sie Singleton-Typen anstelle von pragma-Bibliotheks-Skripten
Wenn Sie ein Pragma-Bibliotheks-Skript verwenden, um anwendungsweite Instanzdaten zu speichern, sollten Sie stattdessen einen QObject Singleton-Typ verwenden. Dies sollte zu einer besseren Leistung führen und hat zur Folge, dass weniger JavaScript-Heap-Speicher verwendet wird.
Speicherzuweisung in einer QML-Anwendung
Die Speichernutzung einer QML-Anwendung kann in zwei Teile aufgeteilt werden: die C++-Heap-Nutzung und die JavaScript-Heap-Nutzung. Ein Teil des jeweils zugewiesenen Speichers ist unvermeidlich, da er von der QML-Engine oder der JavaScript-Engine zugewiesen wird, während der Rest von den Entscheidungen des Anwendungsentwicklers abhängt.
Der C++-Heap enthält:
- den festen und unvermeidlichen Overhead der QML-Engine (Implementierungsdatenstrukturen, Kontextinformationen usw.);
- kompilierte Daten und Typinformationen pro Komponente, einschließlich Metadaten für Eigenschaften pro Typ, die von der QML-Engine generiert werden, je nachdem, welche Module und welche Komponenten von der Anwendung geladen werden;
- C++-Daten pro Objekt (einschließlich Eigenschaftswerte) sowie eine Metaobjekthierarchie pro Element, 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 eingebauter JavaScript-Typen);
- den festen und unvermeidbaren Overhead unserer JavaScript-Integration (Konstruktorfunktionen für geladene Typen, Funktionsschablonen usw.);
- Layout-Informationen pro Typ und andere interne Typdaten, die von der JavaScript-Engine zur Laufzeit für jeden Typ generiert werden (siehe Anmerkung unten zu Typen);
- JavaScript-Daten pro Objekt ("var"-Eigenschaften, JavaScript-Funktionen und Signalhandler sowie nicht optimierte Bindungsausdrücke);
- Variablen, die während der Auswertung von Ausdrücken zugewiesen werden.
Außerdem wird ein JavaScript-Heap für die Verwendung im Haupt-Thread und optional ein weiterer JavaScript-Heap für die Verwendung im WorkerScript -Thread zugewiesen. Wenn eine Anwendung kein WorkerScript Element verwendet, fällt dieser Overhead nicht an. Der JavaScript-Heap kann mehrere Megabyte groß sein, so dass Anwendungen, die für Geräte mit begrenztem Speicherplatz geschrieben wurden, trotz ihrer Nützlichkeit beim asynchronen Auffüllen von Listenmodellen am besten auf das Element WorkerScript verzichten sollten.
Beachten Sie, dass sowohl die QML-Engine als auch die JavaScript-Engine automatisch ihre eigenen Zwischenspeicher für Typdaten über beobachtete Typen erzeugen. Jede von einer Anwendung geladene Komponente ist ein eindeutiger (expliziter) Typ, und jedes Element (Komponenteninstanz), das seine eigenen benutzerdefinierten Eigenschaften in QML definiert, ist ein impliziter Typ. Jedes Element (Instanz einer Komponente), das keine benutzerdefinierte Eigenschaft definiert, wird von den JavaScript- und QML-Engines als Typ betrachtet, der explizit von der Komponente definiert wurde, 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 vorigen Beispiel haben die Rechtecke r0
und r1
keine benutzerdefinierten Eigenschaften, so dass die JavaScript- und QML-Engines sie beide als vom gleichen Typ betrachten. Das heißt, r0
und r1
werden beide als vom explizit definierten Typ Rectangle
betrachtet. Die Rechtecke r2
, r3
und r4
haben jeweils benutzerdefinierte Eigenschaften und werden als unterschiedliche (implizite) Typen betrachtet. Beachten Sie, dass r3
und r4
jeweils als unterschiedliche Typen betrachtet werden, obwohl sie identische Eigenschaftsinformationen haben, einfach weil die benutzerdefinierte Eigenschaft nicht in der Komponente deklariert wurde, deren Instanzen sie sind.
Wenn r3
und r4
beide Instanzen einer Komponente RectangleWithString
sind und diese Komponentendefinition die Deklaration einer Stringeigenschaft mit dem Namen customProperty
enthält, dann würden r3
und r4
als vom gleichen Typ betrachtet (d.h. sie wären Instanzen des Typs RectangleWithString
, anstatt ihren eigenen impliziten Typ zu definieren).
Detaillierte Überlegungen zur Speicherzuweisung
Bei Entscheidungen über die Speicherzuweisung oder Leistungsabwägungen ist es wichtig, die Auswirkungen der CPU-Cache-Leistung, des Paging des Betriebssystems und der Garbage Collection der JavaScript-Engine zu berücksichtigen. Mögliche Lösungen sollten sorgfältig verglichen werden, um sicherzustellen, dass die beste Lösung gewählt wird.
Kein Satz allgemeiner Leitlinien kann ein solides Verständnis der zugrundeliegenden Prinzipien der Informatik in Verbindung mit einer praktischen Kenntnis der Implementierungsdetails der Plattform, für die der Anwendungsentwickler entwickelt, ersetzen. Darüber hinaus kann keine noch so große Menge an theoretischen Berechnungen einen guten Satz von Benchmarks und Analysetools ersetzen, wenn es darum geht, Entscheidungen über Kompromisse zu treffen.
Fragmentierung
Fragmentierung ist ein Problem der C++-Entwicklung. Wenn der Anwendungsentwickler keine C++-Typen oder Plugins definiert, kann er diesen Abschnitt getrost ignorieren.
Im Laufe der Zeit wird eine Anwendung große Teile des Speichers zuweisen, Daten in diesen Speicher schreiben und anschließend einige Teile des Speichers wieder freigeben, sobald sie einen Teil der Daten verwendet hat. Dies kann dazu führen, dass "freier" Speicher in nicht zusammenhängenden Abschnitten liegt, die nicht an das Betriebssystem zurückgegeben werden können, um von anderen Anwendungen genutzt zu werden. Es hat auch Auswirkungen auf die Caching- und Zugriffseigenschaften der Anwendung, da die "lebenden" Daten über viele verschiedene Seiten des physischen Speichers verteilt sein können. Dies wiederum könnte das Betriebssystem zum Auslagern zwingen, was zu Dateisystem-E/A führen kann - was vergleichsweise ein extrem langsamer Vorgang ist.
Die Fragmentierung kann vermieden werden, indem Pool-Allokatoren (und andere Allokatoren für zusammenhängenden Speicher) verwendet werden, indem die Menge des jeweils zugewiesenen Speichers durch eine sorgfältige Verwaltung der Objektlebensdauer reduziert wird, indem die Caches regelmäßig bereinigt und neu aufgebaut werden oder indem eine speicherverwaltete Laufzeitumgebung mit Garbage Collection (wie JavaScript) verwendet wird.
Garbage Collection
JavaScript bietet Garbage Collection. Speicher, der auf dem JavaScript-Heap (im Gegensatz zum C++-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 weniger wichtig ist. Es bedeutet aber auch, dass die JavaScript-Engine zu einem Zeitpunkt, der außerhalb der Kontrolle des Anwendungsentwicklers liegt, einen potenziell langwierigen Vorgang auslösen kann. Wenn die JavaScript-Heap-Nutzung vom Anwendungsentwickler nicht sorgfältig bedacht wird, können Häufigkeit und Dauer der Garbage Collection negative Auswirkungen auf die Anwendungserfahrung haben.
Manuelles Aufrufen des Garbage Collectors
Eine in QML geschriebene Anwendung wird (höchstwahrscheinlich) irgendwann eine Garbage Collection benötigen. Obwohl die Garbage Collection automatisch von der JavaScript-Engine ausgelöst wird, wenn der verfügbare freie Speicherplatz knapp wird, ist es gelegentlich besser, wenn der Anwendungsentwickler selbst entscheidet, wann er den Garbage Collector manuell aufruft (obwohl dies normalerweise 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 verwendet, was zu regelmäßigen und störenden Garbage-Collection-Zyklen während besonders leistungssensibler Aufgaben führt (z. B. Scrollen von Listen, Animationen usw.), kann es für den Anwendungsentwickler sinnvoll sein, den Garbage-Collector in Zeiten ohne Aktivität manuell aufzurufen. Leerlaufzeiten sind ideal für die Durchführung der Garbage Collection, da der Benutzer keine Beeinträchtigung der Benutzererfahrung (übersprungene Frames, ruckelnde Animationen usw.) bemerkt, die sich aus dem Aufruf des Garbage Collectors während der Aktivität ergeben würde.
Der Garbage Collector kann manuell durch den Aufruf von gc()
in JavaScript aufgerufen werden. Dies führt dazu, dass ein umfassender Sammelzyklus durchgeführt wird, der zwischen einigen hundert und mehr als tausend Millisekunden dauern kann und daher möglichst vermieden werden sollte.
Abwägung zwischen Speicher und Leistung
In manchen Situationen ist es möglich, einen Kompromiss zwischen erhöhtem Speicherverbrauch und verringerter Verarbeitungszeit zu finden. Wenn beispielsweise das Ergebnis einer Symbolsuche, die in einer engen Schleife verwendet wird, in einer temporären Variable in einem JavaScript-Ausdruck zwischengespeichert wird, führt dies zu einer erheblichen Leistungssteigerung bei der Auswertung dieses Ausdrucks, erfordert aber die Zuweisung einer temporären Variable. In einigen Fällen sind diese Kompromisse sinnvoll (wie im obigen Fall, der fast immer sinnvoll ist), aber in anderen Fällen kann es besser sein, die Verarbeitung etwas länger dauern zu lassen, um den Speicherdruck auf das System nicht zu erhöhen.
In einigen Fällen können die Auswirkungen eines erhöhten Speicherbedarfs extrem sein. In manchen Situationen kann der Austausch von Speicherplatz gegen einen vermeintlichen Leistungsgewinn zu erhöhtem Page-Thrash oder Cache-Thrash führen, was eine enorme Leistungsminderung zur Folge hat. Es ist immer notwendig, die Auswirkungen von Kompromissen sorgfältig zu prüfen, um zu bestimmen, welche Lösung in einer bestimmten Situation die beste ist.
Ausführliche Informationen zur Cache-Leistung und zu Kompromissen bei der Speicherzeit finden Sie in den folgenden Artikeln:
- Ulrich Dreppers ausgezeichneter Artikel: "Was jeder Programmierer über Speicher wissen sollte", unter: https://people.freebsd.org/~lstewart/articles/cpumemory.pdf.
- Agner Fogs ausgezeichnete Handbücher zur Optimierung von C++-Anwendungen unter: http://www.agner.org/optimize/.
© 2025 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.