Résoudre les problèmes de performances : problèmes de performances courants affectant les applications de style Metro

Blog des développeurs d'applications Windows 8

Indications sur la conception d'applications de style Metro pour Windows 8, par l'équipe d'ingénierie de Windows 8

Résoudre les problèmes de performances : problèmes de performances courants affectant les applications de style Metro

  • Comments 0

Maintenant que vous avez eu le temps de lire mon précédent billet, « Comment améliorer les performances de votre application de style Metro », consacré aux méthodes et outils à votre disposition pour créer des applications rapides et fluides, je souhaite évoquer avec vous les freins à la performance que j'ai pu constater dans les applications. Dans ce billet, je vais passer en revue les principaux conseils permettant d'améliorer de façon mesurable et significative les applications de style Metro, qu'elles soient écrites en JavaScript ou en XAML. Je dévoilerai également cinq pratiques précises qui font la différence, quel que soit le langage utilisé. Bonne nouvelle : leur mise en œuvre ne repose pas sur des stratagèmes ingénieux, ni sur des opérations complexes. Je suis convaincu qu'en suivant ces conseils, vous réussirez à améliorer grandement les performances de vos applications. N'hésitez pas à décrire dans vos commentaires en quoi ces conseils vous ont été utiles et à partager vos propres astuces.

Conseils généraux

Privilégiez les contenus packagés plutôt que les contenus réseau.

  • Les images et les fichiers locaux sont toujours plus rapides à récupérer que ceux situés sur le réseau.
  • Si votre application doit charger une image dynamique, il vaut mieux utiliser une image locale comme espace réservé pendant la récupération.

Affichez les images locales à la taille adéquate.

  • Si une image est systématiquement affichée dans la même résolution, incluez-la dans le package après l'avoir dimensionnée en fonction de cette résolution. Ainsi, l'image ne sera pas mise à l'échelle au moment de l'exécution à chaque fois qu'elle est affichée, ce qui pénaliserait les performances à de nombreuses reprises, tout au long du cycle de vie de l'application.
  • Si l'image risque d'être affichée dans plusieurs résolutions, insérez dans le package plusieurs versions de l'image, sauf si vous avez une bonne raison de ne pas le faire.

Limitez le temps de lancement de votre application.

  • Faites en sorte que les opérations réseau ne soient réalisées qu'après la disparition de l'écran de démarrage.
  • Tant que l'application est en cours d'activation, retardez le chargement des bases de données et des autres gros objets à charger en mémoire.
  • Si vous devez accomplir de lourdes tâches, fournissez un écran de démarrage personnalisé ou une page d'arrivée allégée, de façon à ce que ces tâches puissent être réalisées en arrière-plan.

Windows 8 fonctionne sur un large éventail d'appareils. Aussi, veillez à utiliser des contenus multimédias adaptés à la résolution de l'utilisateur.

  • Le chargement de contenus trop petits par rapport à la résolution de l'utilisateur engendre une perte de fidélité.
  • Le chargement de contenus trop grands par rapport à la résolution de l'utilisateur impose une charge de travail inutile aux ressources du système.

Mettez l'accent sur la réactivité au sein de vos applications.

  • Ne bloquez pas le thread d'interface utilisateur à cause d'API synchrones. Utilisez des API asynchrones ou appelez des API synchrones dans un contexte qui ne provoque pas de blocage (à partir d'un autre thread, par exemple).
  • Déchargez les calculs lourds auprès d'un autre thread que le thread de l'interface utilisateur. Ce principe est important, car les utilisateurs remarqueront probablement les retards supérieurs à 100 ms.
  • Scindez les tâches les plus longues en parties plus petites, pour permettre au thread d'interface utilisateur d'écouter les entrées utilisateur entre chaque partie.
  • Utilisez des travailleurs/threads Web pour prendre en charge les tâches gourmandes en ressources.
  • Ne tracez pas les éléments à l'écran plus vite que ne l'autorise la fréquence de rafraichissement de l'écran. Les événements d'entrée se déclenchent bien plus rapidement que la fréquence de rafraichissement de l'écran. Par conséquent, l'utilisation de ces événements pour actualiser l'écran génère un travail inutilement lourd. Synchronisez plutôt vos tracés avec la fréquence de rafraichissement de l'écran.

Optimisation des performances des applications de style Metro utilisant JavaScript

Lorsque vous codez des applications de style Metro en JavaScript, vous pouvez évidemment faire appel aux conseils et aux astuces que vous appliquez déjà pour développer des sites Web en JavaScript. Dans le cas des applications de style Metro écrites en JavaScript, voici les trois techniques offrant les gains de performance les plus remarquables, d'après nos recherches et nos observations. Pour plus d'informations, consultez l'article consacré aux meilleures pratiques en matière de performances pour les applications de style Metro utilisant JavaScript, disponible dans le Centre de développement.

Utilisez des miniatures pour accélérer le rendu

Le système de fichiers et les fichiers multimédias jouent un rôle essentiel dans la plupart des applications et font également partir des sources de problèmes les plus fréquentes en matière de performances. L'accès aux fichiers multimédias peut être lent, car le stockage et le décodage ou l'affichage des contenus nécessitent de la mémoire et des cycles processeur.

Au lieu de réduire une version grand format d'une image pour l'afficher sous forme de miniature, utilisez plutôt les API Windows Runtime de gestion des miniatures. La réduction d'une image grand format est une solution peu efficace, car l'application doit lire et décoder cette image, puis passer du temps à la redimensionner. Windows Runtime offre différentes API accompagnées par un cache performant, qui permet à l'application d'obtenir rapidement une version plus petite d'une image pour l'utiliser comme miniature.

Dans cet exemple, l'utilisateur doit choisir une image, qui est ensuite affichée.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
var imgTag = document.getElementById("imageTag");
imgTag.src = URL.createObjectURL(file, false);
});

Cet exemple fonctionne bien lorsque vous souhaitez afficher l'image à une taille similaire ou égale à sa taille normale. En revanche, il est peu efficace pour afficher une miniature de l'image. Les API de gestion des miniatures renvoient une version miniature de l'image, que l'application peut décoder et afficher plus rapidement que l'image au format normal. Dans l'exemple suivant, nous utilisons la méthode getThumbnailAsync pour récupérer l'image et créer une miniature à partir de cette dernière.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
var properties = Windows.Storage.FileProperties.ThumbnailMode;
return file.getThumbnailAsync(properties.singleItem, 1024);
})
.then(function (thumb) {
var imgTag = document.getElementById("imageTag");
imgTag.src = URL.createObjectURL(thumb, false);
});

Nos mesures montrent que cette solution peut réduire le temps de chargement des images jusqu'à 1000 % (l'image se charge donc jusqu'à dix fois plus vite lorsque les API de gestion des miniatures sont utilisées en lieu et place d'un accès direct à partir du disque, suivi d'un redimensionnement).

Limitez des interactions DOM au strict minimum

Dans la plateforme des applications de style Metro utilisant JavaScript, le DOM et le moteur JavaScript sont des composants séparés. Toute opération JavaScript impliquant une communication entre ces composants est relativement gourmande en ressource, par rapport aux opérations pouvant être réalisées exclusivement dans le runtime JavaScript. Par conséquent, il est nécessaire de limiter au strict minimum les interactions entre ces composants. Il est par exemple relativement coûteux en ressources de récupérer ou de définir les propriétés des éléments DOM. Dans cet exemple, le code accède de façon répétée à la propriété body et à d'autres propriétés.

// Don’t use: inefficient code. 
function calculateSum() {
// Retrieve Values
var lSide = document.body.all.lSide.value;
var rSide = document.body.all.rSide.value;

// Generate Result
document.body.all.result.value = lSide + rSide;
}

function updateResultsStyle() {
if (document.body.all.result.value > 10) {
document.body.resultsDiv.class = "highlighted";
} else {
document.body.resultsDiv.class = "normal";
}
}

L'exemple suivant améliore le code en mettant en cache la valeur des propriétés au lieu d'y accéder de façon répétée.

function calculateSum() {
// Retrieve Values
var all = document.body.all;
var lSide = all.lSide.value;
var rSide = all.rSide.value;

// Generate Result
all.result.value = lSide + rSide;
}

function updateResultsStyle() {
var body = document.body;
var all = body.all;
if (all.result.value > 10) {
body.resultsDiv.class = "highlighted";
} else {
body.resultsDiv.class = "normal";
}
}

Utilisez des objets DOM uniquement pour stocker les informations qui influent directement sur la façon dont le DOM dispose ou trace les éléments. Si les propriétés lSide et rSide de l'exemple précédent stockent uniquement des informations sur l'état interne de l'application, ne les attachez pas à un objet DOM. L'exemple suivant utilise des objets JavaScript purs pour stocker l'état interne de l'application. L'exemple met à jour les éléments DOM uniquement lorsque l'affichage doit être actualisé.

var state = {
lValue: 0,
rValue: 0,
result: 0
};

function calculateSum() {
state.result = lValue + rValue;
}

function updateResultsStyle() {
var body = document.body;
if (result > 10) {
body.resultsDiv.class = "highlighted";
} else {
body.resultsDiv.class = "normal";
}
}

Nos mesures montrent que le simple fait d'accéder aux données du DOM peut accroître de 700 % le temps d'accès, par rapport à l'accès à des variables qui ne sont pas liées au DOM.

Gérez les dispositions de façon efficace

Pour afficher une application à l'écran, le système doit réaliser des opérations complexes mettant en œuvre les règles HTML et CSS, ainsi que d'autres spécifications relatives à la taille et à la position des éléments dans le DOM. Ce processus, appelé passe de disposition, peut être très coûteux en ressources.

API déclenchant une passe de disposition :

  • window.getComputedStyle
  • offsetHeight
  • offsetWidth
  • scrollLeft
  • scrollTop

Une passe de disposition a lieu dans le cadre de l'appel de ces API, si un élément affectant la disposition a changé depuis la dernière collection d'informations de disposition. Pour réduire le nombre de passes de disposition, vous pouvez regrouper les appels d'API déclenchant une passe de disposition. Examinez l'extrait de code suivant pour mieux comprendre comment procéder : Dans ces deux exemples, nous ajustons les valeurs offsetHeight et offsetWidth d'un élément de cinq unités. Commençons par examiner une solution courante, mais peu efficace, pour ajuster les valeurs offsetHeight et offsetWidth :

// Don't use: inefficient code.
function updatePosition(){
// Calculate the layout of this element and retrieve its offsetHeight
var oH = e.offsetHeight;

// Set this element's offsetHeight to a new value
e.offsetHeight = oH + 5;

// Calculate the layout of this element again because it was changed by the
// previous line, and then retrieve its offsetWidth
var oW = e.offsetWidth;

// Set this element's offsetWidth to a new value
e.offsetWidth = oW + 5;
}

// At some later point the Web platform calculates layout again to take this change into account and render
the element to the screen

L'exemple précédent déclenche trois passes de disposition. Examinons maintenant comment parvenir au même résultat avec plus d'efficacité :

function updatePosition() {
// Calculate the layout of this element and retrieve its offsetHeight
var height = e.offsetHeight + 5;

// Because the previous line already did the layout calculation and no fields were changed, this line retrieves
the offsetWidth from the previous line

var width = e.offsetWidth + 5;

//set this element's offsetWidth to a new value
e.offsetWidth = height;

//set this element's offsetHeight to a new value
e.offsetHeight = width;
}

// At some later point the system calculates layout again to take this change into account and render element to the screen

Même si le deuxième exemple ne présente que de légères différences avec le premier, il déclenche seulement deux passes de disposition au lieu de trois, soit une amélioration de 33 %. L'impact sur les performances varie, selon la taille et la complexité du DOM et de ses styles associés. Plus l'interface utilisateur de votre application est riche, plus vous avez intérêt à suivre ce conseil.

Optimisation des performances des applications de style Metro écrites en XAML

Un code XAML correctement structuré offre de nombreux avantages dans différents scénarios clés : temps d'activation, navigation d'une page à l'autre et utilisation de la mémoire. Voici quelques conseils pour vous aider à optimiser le code XAML de votre application.

Évitez les doublons

L'analyse du code XAML et la création des objets correspondants en mémoire peut prendre beaucoup de temps, si l'interface utilisateur est complexe et présente des arborescences d'éléments composées de nombreux niveaux. Par conséquent, nous vous recommandons de charger uniquement le code XAML nécessaire pour passer l'étape du processus de démarrage. La solution la plus simple consiste à charger uniquement les pages nécessaires pour afficher le premier visuel. Examinez de façon approfondie le code XAML de votre première page pour vérifier qu'elle contient tous les éléments nécessaires. Si vous faites référence à un contrôle ou à un style défini ailleurs, le framework analyse également ce fichier.

<!--This is the first page an app displays. A resource used by this page, TextColor, is defined in 
AppStyles.xaml which means that file must be parsed when this page is loaded. Because AppStyles.xaml
contains many app-wide resources, all of these resources need to be parsed even though they aren’t
necessary to start the app. -->

<Page ...>
<Grid>
<TextBox Foreground="{StaticResource TextColor}" />
</Grid>
</Page>

Contents of AppStyles.xaml
<ResourceDictionary>
<SolidColorBrush x:Key="TextColor" Color="#FF3F42CC"/>

<!--many other resources used across the app and not necessarily for startup.-->
</ResourceDictionary>

Découpez les dictionnaires de ressources. Stockez les ressources utilisées dans l'ensemble de l'application dans l'objet Application afin d'éviter les doublons, mais déplacez les ressources propres à une page spécifique dans le dictionnaire de ressources de la page correspondante. Vous réduisez ainsi la quantité de code XAML analysée lorsque l'application démarre. L'analyse du code XAML consomme des ressources uniquement lorsqu'un utilisateur accède à la page en question.

<!--Bad: XAML which is specific to a page should not be included in the App’s resource
dictionary. The app incurs the cost of parsing resources that are not immediately
necessary at startup, instead of parsing on demand. These page specific resources should be
moved to the resource dictionary for that page.-->
<Application>
<Application.Resources>
<SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="ThirdPageTextColor" Color="#FF3F42CC"/>
</Application.Resources>
</Application>

XAML for the home page of the app
<Page ...>
<StackPanel>
<TextBox Foreground="{StaticResource HomePageTextColor}" />
</StackPanel>
</Page>

XAML for the second page of the app
<Page ...>
<StackPanel>
<Button Content="Submit" Foreground="{StaticResource SecondPageColor}" />
</StackPanel>
</Page>
 
<!--Good: All page specific XAML has been moved to the resource dictionary for the
page on which it’s used. The home page specific XAML was not moved because it must
be parsed at start up anyway. Moving it to the resource dictionary of the home page
wouldn’t be bad either.-->
<Application>
<Application.Resources>
<SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/>
</Application.Resources>
</Application>

XAML for the home page of the app
<Page ...>
<StackPanel>
<TextBox Foreground="{StaticResource HomePageTextColor}" />
</StackPanel>
</Page>

XAML for the second page of the app
<Page ...>
<Page.Resources>
<SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/>
</Page.Resources>

<StackPanel>
<Button Content="Submit" Foreground="{StaticResource SecondPageTextColor}" />
</StackPanel>
</Page>

Optimisez le nombre d'éléments

Le framework XAML a été conçu pour afficher des milliers d'objets. Néanmoins, en réduisant le nombre d'éléments par page, vous accélérez la disposition de votre application et le rendu des scènes. Il existe plusieurs astuces permettant de réduire le nombre d'éléments d'une scène tout en conservant la même complexité visuelle.

  • Évitez les éléments non nécessaires. Par exemple, définissez la propriété Background des panneaux de façon à fournir une couleur d'arrière-plan plutôt que de placer des éléments Rect colorés derrière les éléments du panneau.
<!--Bad XAML uses an unnecessary Rectangle to give the Grid a black background-->                                                                
<Grid>
<Rectangle Fill="Black"/>
</Grid>
 
<!--Good XAML uses the Grid’s background property-->
<Grid Background="Black" />
  • Réduisez les éléments qui ne sont pas visibles, car ils ne sont pas bloqués, ni transparents.

  • Si un même élément vectoriel est utilisé à plusieurs reprises, envisagez de créer une image. La mémoire d'une image n'est allouée qu'une seule fois par URI d'image. Par opposition, les éléments vectoriels sont instanciés à chaque instance.

Utilisez des animations indépendantes

Les animations peuvent être calculées du début à la fin lorsqu'elles sont créées. Il arrive que les modifications apportées à la propriété animée n'influent pas sur les autres objets de la scène. On parle alors d'animations indépendantes. Elles sont exécutées sur le thread de composition et non pas sur le thread d'interface utilisateur. Vous êtes ainsi certain de conserver des animations fluides, car le thread de composition est mis à jour à intervalle régulier. Les animations suivantes sont toujours indépendantes :

  • Animation d'objets à l'aide d'images clés
  • Animations de durée nulle
  • Canvas.Left/Top
  • UIElement.Opacity
  • Propriété SolidColorBrush.Color, appliquée à de nombreuses propriétés
  • Sous-propriétés des propriétés suivantes :
    • UIElement.RenderTransform
    • UIElement.Projection
    • RectangleGeometry de la propriété UIElement.Clip

Les animations dépendantes influent sur la disposition et ne peuvent pas être calculées sans l'aide d'informations fournies par le thread d'interface utilisateur. Il s'agit notamment des modifications apportées aux propriétés telles que width et height. Par défaut, les animations dépendantes ne sont pas exécutées et nécessitent l'approbation du développeur d'applications. Lorsqu'elles sont activées, elles sont exécutées de façon fluide si le thread d'interface utilisateur n'est pas bloqué. En revanche, si le framework ou l'application exécute un grand nombre de tâches, elles subissent des saccades.

Le framework XAML s'efforce de rendre toutes les animations indépendantes par défaut, mais vous pouvez prendre un certain nombre de mesures pour désactiver cette optimisation. Faites particulièrement attente aux opérations suivantes :

  • La définition de la propriété EnableDependentAnimation permet d'exécuter une animation dépendante sur le thread d'interface utilisateur. Convertissez ces animations en une version indépendante. Par exemple, animez ScaleTransform.XScale et ScaleTransform.YScale en lieu et place des propriétés Width et Height d'un objet.
  • Mises à jour image par image correspondant en réalité à des animations dépendantes. C'est par exemple le cas lorsque vous appliquez des transformations dans le gestionnaire pour l'événement CompositonTarget.Rendering.
  • Exécution d'une animation considérée comme indépendante dans un élément où CacheMode=BitmapCache. Elle est alors considérée comme dépendante, car le cache doit faire l'objet d'une nouvelle rastérisation à chaque image.

La création d'applications rapides et fluides, mais également riches et complexes, est tout un art. J'ai partagé avec vous quelques conseils à prendre en compte, mais pour atteindre d'excellentes performances, il faut généralement employer plusieurs outils et techniques. Dans la plupart des cas, il faut mettre en œuvre tout un ensemble de bonnes pratiques et en premier lieu prendre en compte les performances dès les premières phases de conception. J'espère que ce blog vous mettra sur la bonne voie et que vous répondrez aux exigences toujours plus élevées de vos clients.

Je vous souhaite un bon codage, tout en efficacité !

-Dave Tepper, chef de projet, Windows

  • Loading...
Leave a Comment
  • Please add 3 and 3 and type the answer here:
  • Post