Considérations et suggestions relatives aux performances
Considérations relatives à la synchronisation
En tant que développeur d’applications, vous vous efforcez généralement de permettre au moteur de rendu d’atteindre un taux de rafraîchissement constant de 60 images par seconde. Ce chiffre peut varier en fonction de votre matériel et de vos besoins, mais 60 images par seconde est une valeur très courante. Un taux de 60 images par seconde signifie qu’il s’écoule environ 16 millisecondes entre chaque image, pendant lesquelles le traitement peut s’effectuer, y compris le traitement nécessaire au transfert des primitives de dessin vers le matériel graphique.
En pratique, cela signifie que le développeur d’applications doit :
- utiliser autant que possible une programmation asynchrone et pilotée par les événements
- utiliser des threads de travail pour effectuer les traitements importants
- ne jamais relancer manuellement la boucle d’événements
- ne jamais passer plus de quelques millisecondes par image dans des fonctions bloquantes
Le non-respect de ces consignes entraînera des images sautées, ce qui aura un impact considérable sur l'expérience utilisateur.
Remarque : une pratique tentante, mais à éviter à tout prix, consiste à créer votre propre boucle d'événements ( QEventLoop ) ou à appeler ` QCoreApplication::processEvents()` afin d’éviter le blocage au sein d’un bloc de code backend, tel que du C++, appelé depuis QML. Cette pratique est dangereuse, car lorsqu’une boucle d’événements est lancée dans un gestionnaire de signal ou une liaison, le moteur QML continue d’exécuter d’autres liaisons, animations, transitions, etc. Ces liaisons peuvent alors provoquer des effets secondaires qui, par exemple, détruisent la hiérarchie contenant votre boucle d’événements.
Profilage
Le conseil le plus important est le suivant : utilisez QML Profiler l’outil inclus dans Qt Creator (ou un visualiseur de traces disponible sur Qt Extension for Visual Studio Code). Savoir où le temps est passé dans une application vous permettra de vous concentrer sur les zones problématiques qui existent réellement, plutôt que sur celles qui pourraient exister. Consultez Qt Creator: Profilage des applications QML et Qt Extension for Visual Studio Code: Profiler du code QML pour plus d’informations.
Déterminer quelles liaisons sont exécutées le plus souvent, ou dans quelles fonctions votre application passe le plus de temps, vous permettra de décider si vous devez optimiser les zones problématiques ou repenser certains détails d’implémentation de votre application afin d’améliorer les performances. Tenter d’optimiser le code sans profilage risque d’aboutir à des améliorations de performances très mineures plutôt qu’à des améliorations significatives.
Code JavaScript
La plupart des applications QML contiennent du code JavaScript, sous la forme d’expressions de liaison de propriétés, de fonctions et de gestionnaires de signaux. Cela ne pose généralement pas de problème. Grâce à des outils avancés tels que le Qt Quick Compiler, les fonctions et les liaisons simples peuvent s’avérer très rapides. Il faut toutefois veiller à ce qu’aucun traitement inutile ne soit déclenché par inadvertance. Le QML Profiler peut fournir de nombreux détails sur l’exécution du JavaScript et sur ce qui l’a déclenchée.
Conversion de types
L’un des principaux coûts liés à l’utilisation de JavaScript réside dans le fait que, dans certains cas, lorsqu’on accède à une propriété d’un type QML, un objet JavaScript est créé avec une ressource externe contenant les données C++ sous-jacentes (ou une référence à celles-ci). Dans la plupart des cas, cela ne coûte pas grand-chose, mais dans d’autres, cela peut s’avérer assez coûteux. Il convient d’être prudent lors de la gestion de types de valeurs ou de types de séquences volumineux et complexes. Ceux-ci doivent être copiés par le moteur QML chaque fois que vous les modifiez in situ ou que vous les attribuez à une autre propriété. Lorsque cela devient un goulot d’étranglement, envisagez d’utiliser plutôt des types d’objets. Les listes de types d’objets ne posent pas le même problème que les listes de types de valeurs, car elles sont implémentées à l’aide de ` QQmlListProperty`.
La plupart des conversions entre types de valeur simples sont peu coûteuses. Il existe toutefois des exceptions. La création d’une URL à partir d’une chaîne de caractères peut impliquer la construction d’une instance de ` QUrl `, ce qui est coûteux.
Résolution des propriétés
La résolution des propriétés prend du temps. Bien que les recherches soient généralement optimisées pour s’exécuter beaucoup plus rapidement lors des exécutions suivantes, il est toujours préférable d’éviter tout travail superflu, si possible.
Dans l’exemple suivant, nous avons un bloc de code qui s’exécute souvent (dans ce cas, il s’agit du contenu d’une boucle explicite ; mais il pourrait s’agir, par exemple, d’une expression de liaison fréquemment évaluée) et, à l’intérieur de celui-ci, nous résolvons plusieurs fois l’objet portant l’identifiant « rect » et sa propriété « color » :
// 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");
}
}À chaque fois que ` rect.color ` est récupéré, le moteur QML doit :
- Allouer un wrapper de type valeur sur le tas JavaScript.
- Exécuter le getter de la propriété «
color» de ` Rectangle`. - Copier l’ QColor résultant dans le wrapper de type valeur.
Nous n’avons pas besoin de répéter cette opération quatre fois. Nous pouvons au contraire résoudre la base commune une seule fois dans le bloc :
// 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");
}
}Ce simple changement se traduit par une amélioration significative des performances. Notez que le code ci-dessus peut être encore amélioré (puisque la propriété recherchée ne change jamais pendant le traitement de la boucle), en déplaçant la résolution de la propriété en dehors de la boucle, comme suit :
// 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");
}
}Liaisons de propriétés
Une expression de liaison de propriété sera réévaluée si l’une des propriétés auxquelles elle fait référence est modifiée. C’est pourquoi les expressions de liaison doivent rester aussi simples que possible.
Si vous avez une boucle dans laquelle vous effectuez un traitement, mais que seul le résultat final de ce traitement est important, il est souvent préférable de mettre à jour un accumulateur temporaire que vous affectez ensuite à la propriété à mettre à jour, plutôt que de mettre à jour la propriété elle-même de manière incrémentielle, afin d’éviter de déclencher la réévaluation des expressions de liaison au cours des étapes intermédiaires de l’accumulation.
L’exemple artificiel suivant illustre ce point :
// 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];
}
}
}La boucle dans le gestionnaire onCompleted entraîne la réévaluation de la liaison de la propriété « text » à six reprises (ce qui a pour conséquence que toutes les autres liaisons de propriétés dépendant de la valeur de « text », ainsi que le gestionnaire de signal onTextChanged, sont réévaluées à chaque fois, et que le texte est mis en page pour l’affichage à chaque fois). Cela est clairement inutile dans ce cas, puisque seule la valeur finale de l’accumulation nous intéresse réellement.
On pourrait réécrire le code comme suit :
// 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;
}
}Conseils sur les séquences
Comme mentionné précédemment, les séquences de types de valeur doivent être gérées avec précaution.
Tout d’abord, les types de séquences présentent un comportement différent selon deux cas de figure distincts :
- si la séquence est une Q_PROPERTY d’un QObject (nous appellerons cela une séquence de référence),
- si la séquence est renvoyée par une fonction d’ Q_INVOKABLE d’un « QObject » (nous appellerons cela une « séquence de copie »).
Une séquence de référence est lue et écrite via l’ QMetaObject chaque fois qu’elle change, que ce soit dans votre code JavaScript ou sur l’objet d’origine. À des fins d’optimisation, les séquences de référence (ainsi que les types de valeurs de référence) peuvent être chargées de manière différée. Le contenu réel n’est alors récupéré qu’au moment de leur première utilisation. Cela signifie que la modification de la valeur d’un élément quelconque de la séquence depuis JavaScript entraînera :
- La lecture éventuelle du contenu à partir de l’ QObject (en cas de chargement différé).
- La modification de l’élément situé à l’index spécifié dans cette séquence.
- La réécriture de la séquence entière dans l’ QObject.
Une séquence de copie est bien plus simple, car la séquence réelle est stockée dans les données de ressource de l’objet JavaScript ; il n’y a donc pas de cycle lecture/modification/écriture (les données de ressource sont modifiées directement).
Par conséquent, les écritures sur les éléments d’une séquence de référence seront bien plus lentes que celles sur les éléments d’une séquence de copie. En effet, l’écriture dans un seul élément d’une séquence de référence à N éléments équivaut, en termes de coût, à l’affectation d’une séquence de copie à N éléments à cette séquence de référence ; il est donc généralement préférable de modifier une séquence de copie temporaire, puis d’affecter le résultat à une séquence de référence, au cours du calcul.
Supposons l'existence (et l'enregistrement préalable dans l'espace de noms « Qt.example ») du type C++ suivant :
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;
};L’exemple suivant effectue des écritures sur les éléments d’une séquence de référence dans une boucle serrée, ce qui entraîne de mauvaises performances :
// 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");
}
}La lecture et l’écriture de la propriété ` QObject ` dans la boucle interne, provoquées par l’expression ` "qrealListProperty[j] = j" `, rendent ce code très peu optimal. À la place, voici une solution fonctionnellement équivalente mais bien plus rapide :
// 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");
}
}Un autre modèle courant à éviter est celui des boucles « lecture-modification-écriture », dans lesquelles chaque élément est lu, modifié, puis réécrit dans la propriété de la séquence. À l’instar de l’exemple précédent, cela entraîne des lectures et des écritures de la propriété d’ QObject à chaque itération :
// 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");
}
}Il vaut mieux créer une copie manuelle de la séquence, modifier cette copie, puis réaffecter le résultat à la propriété :
// 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");
}
}Deuxièmement, un signal de changement est émis pour la propriété si l’un de ses éléments change. Si vous disposez de nombreuses liaisons vers un élément particulier d’une propriété de séquence, il est préférable de créer une propriété dynamique liée à cet élément, puis d’utiliser cette propriété dynamique comme symbole dans les expressions de liaison à la place de l’élément de la séquence, car cela n’entraînera une réévaluation des liaisons que si sa valeur change.
Il s’agit d’un cas d’utilisation inhabituel auquel la plupart des clients ne devraient jamais être confrontés, mais qu’il est utile de connaître, au cas où vous vous retrouveriez à faire quelque chose de ce genre :
// 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");
}
}Notez que même si seul l'élément à l'index 2 est modifié dans la boucle, les trois liaisons seront toutes réévaluées, car le signal de changement indique que la propriété dans son ensemble a changé. C'est pourquoi l'ajout d'une liaison intermédiaire peut parfois s'avérer utile :
// 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");
}
}Dans l'exemple ci-dessus, seule la liaison intermédiaire sera réévaluée à chaque fois, ce qui se traduira par un gain de performances significatif.
Conseils sur les types de valeur
Les propriétésde type valeur (police, couleur, vector3d, etc.) présentent une sémantique similaire à celle des propriétés de type séquence en matière d’ QObject s et de notification de modification. Ainsi, les conseils donnés ci-dessus pour les séquences s’appliquent également aux propriétés de type valeur. Bien que cela pose généralement moins de problèmes avec les types de valeur (puisque le nombre de sous-propriétés d’un type de valeur est généralement bien inférieur au nombre d’éléments d’une séquence), toute augmentation du nombre de liaisons réévaluées inutilement aura un impact négatif sur les performances.
Conseils généraux en matière de performances
Les considérations générales relatives aux performances de JavaScript, qui découlent de la conception du langage, s’appliquent également à QML. Les plus importantes sont les suivantes :
- Évitez d’utiliser eval() dans la mesure du possible
- Ne supprimez pas les propriétés des objets
Éléments d’interface courants
Éléments de texte
Le calcul de la mise en page du texte peut être une opération lente. Envisagez d'utiliser le format PlainText plutôt que StyledText dans la mesure du possible, car cela réduit la charge de travail imposée au moteur de mise en page. Si vous ne pouvez pas utiliser PlainText (par exemple parce que vous devez intégrer des images ou utiliser des balises pour spécifier des plages de caractères à mettre en forme (gras, italique, etc.) plutôt que l'ensemble du texte), vous devriez alors utiliser StyledText.
Vous ne devriez utiliser AutoText que si le texte est susceptible d'être (mais ce n'est probablement pas le cas) StyledText, car ce mode entraîne un coût d'analyse syntaxique. Le mode RichText ne doit pas être utilisé, car StyledText offre la quasi-totalité de ses fonctionnalités à un coût bien moindre.
Images
Les images constituent un élément essentiel de toute interface utilisateur. Malheureusement, elles sont également une source importante de problèmes en raison du temps nécessaire à leur chargement, de la quantité de mémoire qu’elles consomment et de la manière dont elles sont utilisées.
Chargement asynchrone
Les images sont souvent assez volumineuses ; il est donc judicieux de s'assurer que leur chargement ne bloque pas le thread de l'interface utilisateur. Définissez la propriété « asynchronous » de l'élément Image de QML sur « true » pour activer le chargement asynchrone des images à partir du système de fichiers local (les images distantes sont toujours chargées de manière asynchrone), à condition que cela n'ait pas d'impact négatif sur l'esthétique de l'interface utilisateur.
Les éléments Image dont la propriété « asynchronous » est définie sur « true » chargeront les images dans un thread de travail à faible priorité.
Taille explicite de la source
Si votre application charge une image volumineuse mais l'affiche dans un élément de petite taille, définissez la propriété « sourceSize » sur la taille de l'élément affiché afin de vous assurer que c'est la version réduite de l'image qui est conservée en mémoire, et non la version volumineuse.
Attention : la modification de la propriété « sourceSize » entraînera le rechargement de l'image.
Évitez la composition à l'exécution
N'oubliez pas non plus que vous pouvez éviter d'effectuer des opérations de composition à l'exécution en fournissant avec votre application une ressource d'image pré-composée (par exemple, en ajoutant des effets d'ombre aux éléments).
Évitez de lisser les images
N'activez l'image.smooth e que si cela est nécessaire. Ce processus est plus lent sur certains matériels et n'a aucun effet visuel si l'image est affichée dans sa taille d'origine.
Peinture
Évitez de dessiner plusieurs fois la même zone. Utilisez « Item » comme élément racine plutôt que « Rectangle » pour éviter de dessiner plusieurs fois l'arrière-plan.
Positionner les éléments à l'aide d'ancrages
Il est plus efficace d'utiliser des ancrages plutôt que des liaisons pour positionner les éléments les uns par rapport aux autres. Prenons l'exemple suivant, où des liaisons sont utilisées pour positionner rect2 par rapport à rect1 :
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
}On obtient un résultat plus efficace en utilisant des ancrages :
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
}Le positionnement à l'aide de liaisons (en attribuant des expressions de liaison aux propriétés x, y, width et height des objets visuels, plutôt qu'en utilisant des ancrages) est relativement lent, bien qu'il offre une flexibilité maximale.
Si la mise en page n’est pas dynamique, la manière la plus performante de la définir consiste à initialiser de manière statique les propriétés x, y, width et height. Les coordonnées des éléments sont toujours relatives à leur parent ; par conséquent, si vous souhaitez obtenir un décalage fixe par rapport aux coordonnées (0, 0) de votre parent, vous ne devez pas utiliser d’ancrages. Dans l’exemple suivant, les objets Rectangle enfants se trouvent au même endroit, mais le code utilisant des ancrages présenté ici n’est pas aussi économe en ressources que le code qui utilise un positionnement fixe via une initialisation statique :
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
}
}Modèles et vues
La plupart des applications comportent au moins un modèle fournissant des données à une vue. Les développeurs d’applications doivent connaître certaines règles sémantiques afin d’optimiser les performances.
Modèles C++ personnalisés
Il est souvent souhaitable d'écrire son propre modèle personnalisé dans un langage de backend, tel que le C++, pour l'utiliser avec une vue en QML. Bien que l'implémentation optimale d'un tel modèle dépende fortement du cas d'utilisation auquel il doit répondre, voici quelques recommandations générales :
- Privilégiez autant que possible l'asynchronisme
- Effectuez tous les traitements dans un thread de travail (à faible priorité)
- Regroupez les opérations du backend afin de minimiser les E/S et les communications inter-processus (potentiellement lentes)
Il est important de noter qu’il est recommandé d’utiliser un thread de travail à faible priorité afin de minimiser le risque de « famine » du thread de l’interface graphique (ce qui pourrait entraîner une dégradation des performances perçues). Par ailleurs, n’oubliez pas que les mécanismes de synchronisation et de verrouillage peuvent être une cause majeure de ralentissement des performances ; il convient donc de veiller à éviter tout verrouillage inutile.
Type QML ListModel
Qt Qml La bibliothèque « Models » fournit un type « ListModel » qui peut être utilisé pour alimenter en données un « ListView ». Il est utile pour le prototypage rapide, mais ne convient pas aux volumes de données importants. Utilisez un « QAbstractItemModel » adapté lorsque cela est nécessaire.
Remplissage au sein d’un thread de travail
ListModel Les éléments peuvent être renseignés dans un thread de travail (à faible priorité) en JavaScript. Le développeur doit appeler explicitement la méthode ` sync() ` sur l'objet ` ListModel ` depuis l'WorkerScript afin que les modifications soient synchronisées avec le thread principal. Consultez la documentation de ` WorkerScript ` pour plus d'informations.
Veuillez noter que l’utilisation d’un élément ` WorkerScript ` entraîne la création d’un moteur JavaScript distinct (car le moteur JavaScript est propre à chaque thread). Cela se traduira par une augmentation de l’utilisation de la mémoire. Cependant, plusieurs éléments ` WorkerScript ` utiliseront tous le même thread de travail ; l’impact sur la mémoire lié à l’utilisation d’un deuxième ou d’un troisième élément ` WorkerScript ` est donc négligeable dès lors qu’une application en utilise déjà un. En revanche, les scripts de travail supplémentaires ne s’exécutent pas en parallèle.
N’utilisez pas de rôles dynamiques
L’élément ` ListModel ` part du principe que les types de rôles au sein de chaque élément d’un modèle donné sont stables à des fins d’optimisation. Si le type peut changer de manière dynamique d’un élément à l’autre, les performances du modèle s’en trouveront considérablement réduites.
Par conséquent, le typage dynamique est désactivé par défaut ; le développeur doit explicitement définir la propriété booléenne dynamicRoles du modèle pour activer le typage dynamique (et subir la perte de performances qui en découle). Nous vous recommandons de ne pas utiliser le typage dynamique sauf en cas d'absolue nécessité.
Vues
Les délégués de vue doivent rester aussi simples que possible. Ne placez dans le délégué que le code QML strictement nécessaire à l’affichage des informations requises. Toute fonctionnalité supplémentaire qui n’est pas immédiatement requise (par exemple, l’affichage d’informations supplémentaires lorsque l’on clique dessus) ne doit pas être créée tant qu’elle n’est pas nécessaire (voir la section suivante sur l’initialisation paresseuse).
La liste suivante résume bien les points à garder à l'esprit lors de la conception d'un délégué :
- Moins un délégué contient d'éléments, plus sa création est rapide, et plus le défilement de la vue est donc rapide.
- Limitez au minimum le nombre de liaisons dans un délégué ; en particulier, utilisez des ancrages plutôt que des liaisons pour le positionnement relatif au sein d’un délégué.
- Évitez d'utiliser des éléments « ShaderEffect » au sein des délégués.
- N'activez jamais le découpage sur un délégué.
Vous pouvez définir la propriété ` cacheBuffer ` d’une vue pour autoriser la création asynchrone et la mise en mémoire tampon des délégués en dehors de la zone visible. L’utilisation d’un ` cacheBuffer ` est recommandée pour les délégués de vue qui ne sont pas triviaux et qui ont peu de chances d’être créés en une seule image.
Gardez à l’esprit qu’un « cacheBuffer » conserve des délégués supplémentaires en mémoire. Par conséquent, la valeur tirée de l’utilisation de l’« cacheBuffer » doit être mise en balance avec l’utilisation supplémentaire de mémoire. Les développeurs doivent recourir à des tests de performance pour déterminer la valeur optimale pour leur cas d’utilisation, car la pression mémoire accrue causée par l’utilisation d’un « cacheBuffer » peut, dans de rares cas, entraîner une baisse de la fréquence d’images lors du défilement.
Pour améliorer encore les performances, pensez à activer la réutilisation des éléments dans les vues. Consultez Reusing Items for ListView et Reusing Items for TableView and TreeView pour plus d’informations.
Effets visuels
Qt Quick comprend plusieurs fonctionnalités qui permettent aux développeurs et aux concepteurs de créer des interfaces utilisateur particulièrement attrayantes. La fluidité, les transitions dynamiques ainsi que les effets visuels peuvent être utilisés à bon escient dans une application, mais il convient de faire preuve de prudence lors de l'utilisation de certaines fonctionnalités de QML, car elles peuvent avoir un impact sur les performances.
Animations
En général, l'animation d'une propriété entraîne la réévaluation de tous les liaisons qui font référence à cette propriété. Bien que cela soit généralement souhaité, il peut parfois être préférable de désactiver la liaison avant d'exécuter l'animation, puis de la réattribuer une fois l'animation terminée.
Évitez d'exécuter du code JavaScript pendant l'animation. Par exemple, il faut éviter d'exécuter une expression JavaScript complexe pour chaque image d'une animation de la propriété x.
Les développeurs doivent faire preuve d’une prudence particulière lorsqu’ils utilisent des animations par script, car celles-ci s’exécutent dans le thread principal (et peuvent donc entraîner le saut de certaines images si leur exécution prend trop de temps).
Particules
Le Qt Quick Particles module permet d’intégrer de superbes effets de particules de manière transparente dans les interfaces utilisateur. Cependant, chaque plateforme dispose de capacités matérielles graphiques différentes, et le module Particles n’est pas en mesure de limiter les paramètres à ce que votre matériel peut prendre en charge de manière fluide. Plus vous essayez de rendre de particules (et plus celles-ci sont volumineuses), plus votre matériel graphique devra être performant pour afficher 60 images par seconde. Le traitement d'un plus grand nombre de particules nécessite un processeur plus rapide. Il est donc important de tester soigneusement tous les effets de particules sur votre plateforme cible, afin de déterminer le nombre et la taille des particules que vous pouvez afficher à 60 images par seconde.
Il convient de noter qu’un système de particules peut être désactivé lorsqu’il n’est pas utilisé (par exemple, sur un élément non visible) afin d’éviter toute simulation inutile.
Consultez le Guide des performances des systèmes de particules pour obtenir des informations plus détaillées.
Contrôle de la durée de vie des éléments
En divisant une application en composants simples et modulaires, chacun contenu dans un seul fichier QML, vous pouvez accélérer le temps de démarrage de l’application, mieux contrôler l’utilisation de la mémoire et réduire le nombre d’éléments actifs mais invisibles dans votre application.
Initialisation paresseuse
Le moteur QML met en œuvre des mécanismes sophistiqués pour s’assurer que le chargement et l’initialisation des composants n’entraînent pas de saut d’images. Cependant, le meilleur moyen de réduire le temps de démarrage consiste à éviter d’effectuer des tâches superflues et à reporter celles-ci jusqu’à ce qu’elles soient nécessaires. Pour ce faire, vous pouvez utiliser soit Loader.
Utilisation de Loader
Le Loader est un élément qui permet le chargement et le déchargement dynamiques de composants.
- Grâce à la propriété « active » d'un Loader, l'initialisation peut être différée jusqu'à ce qu'elle soit nécessaire.
- En utilisant la version surchargée de la fonction « setSource() », il est possible de fournir des valeurs de propriété initiales.
- Définir la propriété « asynchronous » du Loader sur « true » peut également améliorer la fluidité lors de l'instanciation d'un composant.
Détruire les éléments inutilisés
Les éléments invisibles parce qu’ils sont enfants d’un élément non visible (par exemple, le deuxième onglet d’un widget à onglets, alors que le premier onglet est affiché) doivent, dans la plupart des cas, être initialisés de manière différée et supprimés lorsqu’ils ne sont plus utilisés, afin d’éviter le coût permanent lié à leur maintien en activité (par exemple, rendu, animations, évaluation des liaisons de propriétés, etc.).
Un élément chargé à l’aide d’un élément Loader peut être libéré en réinitialisant la propriété « source » ou « sourceComponent » du Loader, tandis que d’autres éléments peuvent être explicitement libérés en appelant la méthode destroy() sur ceux-ci. Dans certains cas, il peut s’avérer nécessaire de laisser l’élément actif ; dans ce cas, il doit au moins être rendu invisible.
Reportez-vous à la section suivante consacrée au rendu pour plus d’informations sur les éléments actifs mais invisibles.
Rendu
Le graphe de scène utilisé pour le rendu dans Qt Quick permet de rendre de manière fluide, à 60 images par seconde, des interfaces utilisateur hautement dynamiques et animées. Certains éléments peuvent toutefois réduire considérablement les performances de rendu ; les développeurs doivent donc veiller à éviter ces écueils autant que possible.
Découpage
Le clipping est désactivé par défaut et ne doit être activé qu'en cas de nécessité.
Le découpage est un effet visuel, et NON une optimisation. Il augmente (plutôt qu’il ne réduit) la complexité pour le moteur de rendu. Si le clipping est activé, un élément limitera son propre dessin, ainsi que celui de ses enfants, à son rectangle de délimitation. Cela empêche le moteur de rendu de réorganiser librement l'ordre de dessin des éléments, ce qui se traduit par un parcours du graphe de scène sous-optimal, même dans le meilleur des cas.
Le clipping à l’intérieur d’un délégué est particulièrement néfaste et doit être évité à tout prix.
Sur-dessin et éléments invisibles
Si certains éléments sont entièrement recouverts par d’autres éléments (opaques), il est préférable de définir leur propriété « visible » sur « false » ; sinon, ils seront affichés inutilement.
De même, les éléments qui sont invisibles (par exemple, le deuxième onglet d'un widget à onglets, lorsque le premier onglet est affiché) mais qui doivent être initialisés au démarrage (par exemple, si l’instanciation du deuxième onglet prend trop de temps pour pouvoir être effectuée uniquement lorsque l’onglet est activé), doivent voir leur propriété « visible » définie sur « false », afin d’éviter le coût lié à leur affichage (bien que, comme expliqué précédemment, ils entraîneront toujours le coût des animations ou de l’évaluation des liaisons puisqu’ils restent actifs).
Translucide ou opaque
Le contenu opaque est généralement beaucoup plus rapide à afficher que le contenu translucide. Cela s’explique par le fait que le contenu translucide nécessite un mélange de couleurs et que le moteur de rendu peut potentiellement mieux optimiser le contenu opaque.
Une image comportant un seul pixel translucide est considérée comme entièrement translucide, même si elle est en grande partie opaque. Il en va de même pour une « BorderImage » aux contours transparents.
Shaders
Le type « ShaderEffect » permet d’intégrer du code GLSL en ligne dans une application « Qt Quick » avec une surcharge minime. Cependant, il est important de garder à l’esprit que le programme de fragment doit s’exécuter pour chaque pixel de la forme rendue. Lors d’un déploiement sur du matériel bas de gamme et lorsque le shader couvre un grand nombre de pixels, il convient de limiter le shader de fragment à quelques instructions afin d’éviter une baisse de performances.
Les shaders écrits en GLSL permettent de créer des transformations complexes et des effets visuels, mais ils doivent être utilisés avec prudence. L'utilisation d'un « ShaderEffectSource » entraîne le pré-rendu de la scène dans un FBO avant qu'elle ne puisse être dessinée. Cette surcharge supplémentaire peut s'avérer très coûteuse en ressources.
Allocation et récupération de mémoire
La quantité de mémoire qui sera allouée par une application et la manière dont cette mémoire sera allouée constituent des considérations très importantes. Outre les préoccupations évidentes liées aux situations de manque de mémoire sur les appareils aux ressources limitées, l'allocation de mémoire sur le tas est une opération relativement coûteuse en termes de calcul, et certaines stratégies d'allocation peuvent entraîner une fragmentation accrue des données entre les pages. JavaScript utilise un tas de mémoire géré qui fait l'objet d'un ramasse-miettes automatique, ce qui présente certains avantages, mais aussi des implications importantes.
Une application écrite en QML utilise à la fois la mémoire du tas C++ et celle d’un tas JavaScript géré automatiquement. Le développeur de l’application doit connaître les subtilités de chacun d’entre eux afin d’optimiser les performances.
Conseils pour les développeurs d’applications QML
Les conseils et suggestions contenus dans cette section ne constituent que des lignes directrices et peuvent ne pas s'appliquer dans toutes les circonstances. Veillez à tester et à analyser soigneusement votre application à l'aide de mesures empiriques afin de prendre les meilleures décisions possibles.
Instanciez et initialisez les composants de manière différée
Si votre application se compose de plusieurs vues (par exemple, plusieurs onglets) mais qu'une seule est nécessaire à un moment donné, vous pouvez utiliser l'instanciation différée pour réduire au minimum la quantité de mémoire que vous devez allouer à un moment donné. Reportez-vous à la section précédente sur l'initialisation différée pour plus d'informations.
Détruire les objets inutilisés
Si vous chargez des composants de manière différée ou si vous créez des objets de manière dynamique au cours d’une expression JavaScript, il est souvent préférable de les « dé destroy() r » manuellement plutôt que d’attendre que le ramasse-miettes automatique s’en charge. Reportez-vous à la section précédente consacrée au contrôle de la durée de vie des éléments pour plus d’informations.
N'invoquez pas manuellement le ramasse-miettes
Dans la plupart des cas, il n’est pas judicieux d’appeler manuellement le ramasse-miettes, car cela bloquera le thread de l’interface graphique pendant un laps de temps considérable. Cela peut entraîner des images sautées et des animations saccadées, ce qu’il faut éviter à tout prix.
Il existe certains cas où l'appel manuel du ramasse-miettes est acceptable (ce qui sera expliqué plus en détail dans une section suivante), mais dans la plupart des cas, cet appel est inutile et contre-productif.
Évitez de définir plusieurs types implicites identiques
Si un élément QML possède une propriété personnalisée définie en QML, celle-ci devient son propre type implicite. Ceci est expliqué plus en détail dans une section suivante. Si plusieurs types implicites identiques sont définis dans un Component, cela entraîne un gaspillage de mémoire. Dans ce cas, il est généralement préférable de définir explicitement un nouveau composant qui pourra ensuite être réutilisé. Envisagez de définir un composant en ligne à l’aide du mot-clé ` component ` dans un tel cas.
La définition d’une propriété personnalisée peut souvent constituer une optimisation de performances utile (par exemple, pour réduire le nombre de liaisons requises ou réévaluées), ou améliorer la modularité et la maintenabilité d’un composant. Dans ces cas-là, l’utilisation de propriétés personnalisées est recommandée. Toutefois, si le nouveau type est utilisé plus d’une fois, il doit être séparé dans son propre composant (inline ou fichier .qml) afin d’économiser de la mémoire.
Réutiliser les composants existants
Si vous envisagez de définir un nouveau composant, il est utile de vérifier qu'un tel composant n'existe pas déjà dans l'ensemble de composants de votre plateforme. Sinon, vous obligerez le moteur QML à générer et à stocker des données de type pour un type qui est essentiellement un doublon d'un autre composant préexistant et potentiellement déjà chargé.
Utilisez des types singleton plutôt que des scripts de bibliothèque pragma
Si vous utilisez un script de bibliothèque « pragma » pour stocker des données d'instance à l'échelle de l'application, envisagez plutôt d'utiliser un type singleton de type « QObject ». Cela devrait améliorer les performances et réduire l'utilisation de la mémoire du tas JavaScript.
Allocation de mémoire dans une application QML
L'utilisation de la mémoire par une application QML peut être divisée en deux parties : l'utilisation du tas natif et celle du tas JavaScript. Une partie de la mémoire allouée dans chacun de ces tas est inévitable, car elle est allouée par le moteur QML ou le moteur JavaScript, tandis que le reste dépend des choix effectués par le développeur de l'application.
Le tas natif contiendra :
- la surcharge fixe et inévitable du moteur QML (structures de données d’implémentation, informations de contexte, etc.) ;
- les données compilées et les informations de type par composant, y compris les métadonnées de propriétés par type, qui sont générées ou chargées depuis le cache disque par le moteur QML en fonction des modules et des composants chargés par l’application ;
- les données C++ par objet (y compris les valeurs de propriété) ainsi qu’une hiérarchie de méta-objets par élément, en fonction des composants que l’application instancie ;
- toutes les données allouées spécifiquement par les importations QML (bibliothèques).
Le tas JavaScript contiendra :
- la surcharge fixe et inévitable du moteur JavaScript lui-même (y compris les types JavaScript intégrés) ;
- la surcharge fixe et inévitable liée à notre intégration JavaScript (fonctions constructrices pour les types chargés, modèles de fonctions, etc.) ;
- les informations de mise en page par type et autres données de type internes générées par le moteur JavaScript lors de l'exécution, pour chaque type (voir la remarque ci-dessous concernant les types) ;
- les données JavaScript par objet (propriétés « var », fonctions JavaScript et gestionnaires de signaux, ainsi que les expressions de liaison non optimisées) ;
- les variables allouées lors de l’évaluation des expressions.
De plus, un tas JavaScript sera alloué pour une utilisation dans le thread principal, et éventuellement un autre tas JavaScript alloué pour une utilisation dans le thread « WorkerScript ». Si une application n’utilise pas d’élément « WorkerScript », cette surcharge ne sera pas générée. Le tas JavaScript pouvant atteindre plusieurs mégaoctets, il peut être préférable, pour les applications destinées à des appareils à mémoire limitée, d’éviter l’élément ` WorkerScript `.
Notez que le moteur QML et le moteur JavaScript génèrent automatiquement leurs propres caches de données de type concernant les types observés. Chaque composant chargé par une application est un type distinct (explicite), et chaque élément (instance de composant) qui définit ses propres propriétés personnalisées en QML est un type implicite. Tout élément (instance d’un composant) qui ne définit aucune propriété personnalisée est considéré par les moteurs JavaScript et QML comme étant du type explicitement défini par le composant, plutôt que de son propre type implicite.
Prenons l’exemple suivant :
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"
}
}Dans l’exemple précédent, les rectangles r0 et r1 ne possèdent aucune propriété personnalisée ; les moteurs JavaScript et QML les considèrent donc tous deux comme étant du même type. Autrement dit, r0 et r1 sont tous deux considérés comme étant du type Rectangle, défini explicitement. Les rectangles r2, r3 et r4 possèdent chacun des propriétés personnalisées et sont donc considérés comme étant de types (implicites) différents. Notez que r3 et r4 sont tous deux considérés comme étant de types différents, même s’ils possèdent des informations de propriétés identiques, simplement parce que la propriété personnalisée n’a pas été déclarée dans le composant dont ils sont des instances.
Si r3 et r4 étaient tous deux des instances d’un composant RectangleWithString, et que la définition de ce composant incluait la déclaration d’une propriété de type chaîne nommée customProperty, alors r3 et r4 seraient considérés comme étant du même type (c’est-à-dire qu’ils seraient des instances du type RectangleWithString, plutôt que de définir leur propre type implicite).
Considérations approfondies sur l’allocation de mémoire
Lorsqu’il s’agit de prendre des décisions concernant l’allocation de mémoire ou les compromis en matière de performances, il est important de garder à l’esprit l’impact des performances du cache du processeur, de la pagination du système d’exploitation et du ramasse-miettes du moteur JavaScript. Les solutions potentielles doivent faire l’objet de tests de performance minutieux afin de s’assurer que la meilleure est retenue.
Aucun ensemble de recommandations générales ne peut se substituer à une solide compréhension des principes fondamentaux de l’informatique, combinée à une connaissance pratique des détails d’implémentation de la plateforme pour laquelle le développeur d’applications travaille. De plus, aucun calcul théorique ne peut remplacer un ensemble efficace de tests de performance et d’outils d’analyse lors de la prise de décisions impliquant des compromis.
Fragmentation
La fragmentation est un problème propre au développement en C++. Si le développeur d’applications ne définit aucun type C++ ni aucun plugin, il peut ignorer cette section sans risque.
Au fil du temps, une application alloue de grandes portions de mémoire, y écrit des données, puis libère certaines de ces portions une fois qu’elle a fini d’utiliser une partie des données. Cela peut entraîner une répartition de la mémoire « libre » en blocs non contigus, qui ne peuvent pas être restitués au système d’exploitation pour être utilisés par d’autres applications. Cela a également un impact sur les caractéristiques de mise en cache et d’accès de l’application, car les données « actives » peuvent être réparties sur de nombreuses pages différentes de la mémoire physique. Cela peut à son tour contraindre le système d’exploitation à recourir à la pagination, ce qui peut entraîner des E/S sur le système de fichiers — opération qui est, comparativement, extrêmement lente.
La fragmentation peut être évitée en utilisant des allocateurs de pool (et d’autres allocateurs de mémoire contiguë), en réduisant la quantité de mémoire allouée à un moment donné grâce à une gestion rigoureuse de la durée de vie des objets, en nettoyant et en reconstruisant périodiquement les caches, ou en utilisant un moteur d’exécution avec gestion de la mémoire et ramasse-miettes (comme JavaScript).
Collecte des déchets
JavaScript dispose d'un ramasse-miettes. La mémoire allouée sur le tas JavaScript (par opposition au tas natif) appartient au moteur JavaScript. Ce dernier récupère périodiquement toutes les données non référencées présentes sur le tas JavaScript.
Implications du ramasse-miettes
Le ramasse-miettes présente des avantages et des inconvénients. Il rend la gestion manuelle de la durée de vie des objets moins importante. Cependant, cela signifie également qu’une opération potentiellement longue peut être lancée par le moteur JavaScript à un moment échappant au contrôle du développeur d’applications. À moins que l’utilisation du tas JavaScript ne soit soigneusement prise en compte par le développeur d’applications, la fréquence et la durée du ramasse-miettes peuvent avoir un impact négatif sur l’expérience utilisateur. Depuis Qt 6.8, le ramasse-miettes est incrémental, ce qui signifie qu’il entraînera des interruptions plus courtes, mais potentiellement plus fréquentes.
Appel manuel du collecteur de mémoire
Une application écrite en QML nécessitera (très probablement) un ramassage des déchets à un moment ou à un autre. Bien que le ramassage des déchets soit automatiquement déclenché par le moteur JavaScript selon son propre calendrier, il est parfois préférable que le développeur de l’application décide lui-même du moment où invoquer manuellement le ramasseur de déchets (même si ce n’est généralement pas le cas).
C’est en effet le développeur qui est le mieux placé pour savoir quand une application va rester inactive pendant des périodes prolongées. Si une application QML utilise beaucoup de mémoire du tas JavaScript, provoquant des cycles de ramasse-miettes réguliers et perturbateurs lors de tâches particulièrement sensibles aux performances (par exemple, le défilement de listes, les animations, etc.), le développeur de l’application a tout intérêt à déclencher manuellement le ramasse-miettes pendant les périodes d’inactivité totale. Les périodes d’inactivité sont idéales pour effectuer un ramassage des ordures, car l’utilisateur ne remarquera aucune dégradation de l’expérience utilisateur (images sautées, animations saccadées, etc.) qui résulterait de l’appel du ramasseur d’ordures pendant une période d’activité.
Le ramasse-miettes peut être lancé manuellement en appelant ` gc() ` dans JavaScript. Cela déclenchera un cycle de ramasse-miettes complet et non incrémental, dont l’exécution peut prendre de quelques centaines à plus d’un millier de millisecondes ; il convient donc de l’éviter autant que possible.
Compromis entre mémoire et performances
Dans certaines situations, il est possible de privilégier une réduction du temps de traitement au détriment d’une augmentation de l’utilisation de la mémoire. Par exemple, la mise en cache du résultat d’une recherche de symbole utilisée dans une boucle serrée dans une variable temporaire au sein d’une expression JavaScript se traduira par une amélioration significative des performances lors de l’évaluation de cette expression, mais cela implique l’allocation d’une variable temporaire. Dans certains cas, ces compromis sont judicieux (comme dans l’exemple ci-dessus, où ils le sont presque toujours), mais dans d’autres, il peut être préférable de laisser le traitement prendre un peu plus de temps afin d’éviter d’accroître la pression sur la mémoire du système.
Dans certains cas, l’impact d’une pression mémoire accrue peut être extrême. Dans certaines situations, privilégier l’utilisation de la mémoire au profit d’un gain de performances supposé peut entraîner une augmentation du « page-thrash » ou du « cache-thrash », provoquant une baisse considérable des performances. Il est toujours nécessaire d’évaluer soigneusement l’impact de ces compromis afin de déterminer quelle solution est la meilleure dans une situation donnée.
Pour des informations approfondies sur les performances du cache et les compromis entre mémoire et temps d’exécution, consultez les articles suivants :
- L'excellent article d’Ulrich Drepper : « Ce que tout programmeur devrait savoir sur la mémoire », disponible à l’adresse : https://people.freebsd.org/~lstewart/articles/cpumemory.pdf.
- Les excellents manuels d’Agner Fog sur l’optimisation des applications C++, disponibles à l’adresse suivante : http://www.agner.org/optimize/.
Démarrage rapide et optimisation du lancement
En vous appuyant sur une expérience concrète en matière d’optimisation d’applications d’ Qt Quick s pour un démarrage rapide, tenez compte des bonnes pratiques suivantes :
- Concevez votre application pour qu’elle démarre rapidement dès le départ. Réfléchissez à ce que vous souhaitez que l’utilisateur voie en premier.
- Utilisez le QML Profiler pour identifier les goulots d’étranglement au démarrage.
- Utilisez le chargement en chaîne. N'exécutez que le nombre d'loaders s correspondant au nombre de cœurs de votre processeur (par exemple, deux cœurs : deux chargeurs s'exécutant simultanément).
- Le premier chargeur d'loader s ne doit pas être asynchrone, afin qu'une partie du contenu s'affiche immédiatement. Déclenchez les chargeurs asynchrones par la suite.
- Ne vous connectez aux services back-end que lorsque cela est nécessaire.
- Créez des modules QML qui sont importés à la demande. Grâce à l’utilisation de modules et de types chargés de manière différée, vous pouvez rendre les services non essentiels disponibles pour votre application au fur et à mesure des besoins.
- Optimisez vos images PNG/JPG à l’aide d’outils tels qu’optipng.
- Optimisez vos modèles 3D en réduisant le nombre de sommets et en supprimant les parties non visibles.
- Optimisez le chargement des modèles 3D en utilisant glTF.
- Limitez l'utilisation du découpage et de l'opacité, car ceux-ci peuvent avoir un impact sur les performances.
- Évaluez les limites du GPU et tenez-en compte lors de la conception de l’interface utilisateur. Consultez Frame Captures and Performance Profiling pour plus d’informations.
- Utilisez Qt Quick Compiler pour précompiler les fichiers QML.
- Vérifiez si la liaison statique est possible pour votre architecture.
- Privilégiez les liaisons déclaratives plutôt que les gestionnaires de signaux impératifs.
- Privilégiez des liaisons de propriétés simples. De manière générale, veillez à ce que le code QML reste simple, agréable à écrire et lisible. Vous obtiendrez ainsi de bonnes performances.
- Remplacez les contrôles complexes par des images ou des shaders si le temps de création pose problème.
À ne pas faire :
- Abuser du QML. Même si vous utilisez le QML, vous n’avez pas besoin de tout faire en QML.
- Initialisez tout dans votre fichier main.cpp.
- Créez de grands singletons contenant toutes les interfaces requises.
- Ne créez pas de délégués complexes pour les vues d'ListView s ou autres.
- N’utilisez pas clip sauf en cas d’absolue nécessité.
- Tombez dans le piège courant qui consiste à abuser des chargeurs. L'Loader est idéal pour le chargement différé d'éléments volumineux tels que les pages d'application, mais il génère une surcharge trop importante pour le chargement d'éléments simples. Ce n'est pas une formule magique qui accélère tout et n'importe quoi. C'est un élément supplémentaire avec un contexte QML supplémentaire.
Ces pratiques permettent d’obtenir des temps de démarrage inférieurs à une seconde et une expérience utilisateur fluide, en particulier sur les appareils embarqués.
© 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.