Aller au contenu principal

Performances

Les dĂ©veloppeurs posent frĂ©quemment des questions Ă  propos des stratĂ©gies Ă  adopter pour optimiser les performances des applications Electron. Les ingĂ©nieurs logiciels, les utilisateurs et les dĂ©veloppeurs de frameworks ne sont pas toujours d’accord sur ce que signifie « performances » et quelle en est la dĂ©finition. Ce document dĂ©crit certaines des maniĂšres privilĂ©giĂ©es par les mainteneurs d'Electron pour rĂ©duire la quantitĂ© des ressources mĂ©moire, CPU, et disque utilisĂ©es tout en s'assurant que l'application est trĂšs rĂ©active aux saisies de l'utilisateur et s'exĂ©cute aussi rapidement que possible. Par ailleurs, ces stratĂ©gies devront assurer un niveau Ă©levĂ© de sĂ©curitĂ© Ă  votre application.

Le bon sens et les maniĂšres de crĂ©er des sites Web performants avec JavaScript s’appliquent pour la plupart aux applications Electron. Dans une certaine mesure, les ressources Ă©voquant les façons de crĂ©er des applications Node.js performantes s’appliquent Ă©galement, mais comprenez bien que la signification du terme « performances Â» est diffĂ©rente selon que l'on est avec un backend Node.js ou avec une application cotĂ© client.

This list is provided for your convenience – and is, much like our security checklist – not meant to be exhaustive. Il est d'ailleurs surement possible de crĂ©er une application Electron s'avĂ©rant lente tout en suivant toutes les Ă©tapes dĂ©crites ci-dessous. Electron est une plate-forme de dĂ©veloppement puissante qui vous permet, en tant que dĂ©veloppeur, de faire plus ou moins ce que vous voulez. Toute cette libertĂ© signifie que les performances sont, pour leur majeure partie, de votre responsabilitĂ©.

Mesurer, Mesurer et encore Mesurer​

La checklist contient un certain nombre d’étapes assez simples et faciles Ă  mettre en Ɠuvre. Cependant, la crĂ©ation de la version la plus performante de votre application vous imposera bien plus que de suivre simplement un certain nombre d’étapes. Au lieu de cela, vous devrez examiner de trĂšs prĂšs tout le code en cours d’exĂ©cution dans votre application en l'analysant et le mesurant soigneusement. Vous devrez donc vous poser par exemple certaines questions : Il y a -t-il des goulots d'Ă©tranglement ? Quelles opĂ©rations prennent le plus de temps lorsque l’utilisateur clique sur un bouton? Quels objets occupent le plus de mĂ©moire alors que l’application tourne simplement au ralenti?

À maintes reprises, nous avons pu observer que la stratĂ©gie la plus efficace pour crĂ©er une application Electron performante consistait Ă  profiler le code en cours d’exĂ©cution, Ă  trouver les parties les plus gourmandes en ressources et Ă  optimiser celles-ci. La rĂ©pĂ©tition de ce processus laborieux et apparemment sans fin augmentera considĂ©rablement les performances de de votre application. L’expĂ©rience acquise avec des applications majeures telles que Visual Studio Code ou Slack a montrĂ© que cette pratique est de loin la stratĂ©gie la plus efficace pour amĂ©liorer les performances.

Pour en savoir plus sur la façon d'analyser et de profiler le code de votre application, familiarisez-vous avec les Outils de dĂ©veloppement de Chrome. Pour une analyse avancĂ©e ou on examine plusieurs processus simultanĂ©ment, envisagez l’outil Chrome Tracing.

Lectures RecommandĂ©es​

Checklist : recommandations pour optimiser les performances​

Il y a des chances que votre application soit un peu plus légÚre, plus rapide et généralement moins gourmande en ressources si vous essayez de suivre ces conseils.

  1. Ne pas inclure des modules de maniÚre inconsidérée
  2. Ne pas charger ni exécuter du code plus tÎt que nécessaire
  3. Eviter de bloquer le processus principal
  4. Eviter de bloquer les processus de rendu
  5. Eviter les polyfills inutiles
  6. Faire la chasse aux requĂȘtes rĂ©seau inutiles ou bloquantes
  7. Regroupez votre code
  8. Appeler Menu.setApplicationMenu(null) lorsque vous n'avez pas besoin d'un menu par défaut

1. Ne pas inclure des modules de maniĂšre inconsidĂ©rĂ©e​

Avant d’ajouter un module Node.js Ă  votre application, examinez bien ledit module. Combien de dĂ©pendances comporte ce module ? De quel type de ressources a-t-il besoin pour simplement ĂȘtre appelĂ© dans une dĂ©claration require() ? Vous constaterez peut-ĂȘtre ainsi que le module le plus de tĂ©lĂ©chargĂ© sur le registre NPM ou le plus Ă©toilĂ© sur GitHub n’est en fait pas du tout celui dont la taille est la plus petite parmi ceux disponibles.

Pourquoi ?​

Le raisonnement derriÚre cette recommandation est mieux illustré par un exemple réel. During the early days of Electron, reliable detection of network connectivity was a problem, resulting in many apps using a module that exposed a simple isOnline() method.

Ce module dĂ©tectait votre connectivitĂ© rĂ©seau en essayant d’atteindre un certain nombre de points de terminaison bien connus. Pour la liste de ces points de terminaison, cela dĂ©pendait d'un module diffĂ©rent, qui contenait Ă©galement une liste de ports bien connus. Cette dĂ©pendance reposait elle-mĂȘme sur un module contenant des informations sur les ports sous la forme d'un fichier JSON avec plus de 100.000 lignes de contenu. Chaque fois que le module Ă©tait chargĂ© (gĂ©nĂ©ralement dans une instruction require('module') ), il chargeait toutes ses dĂ©pendances et finissait par lire et analyser ce fichier JSON. Mais l’analyse de plusieurs milliers de lignes de JSON est une opĂ©ration trĂšs coĂ»teuse. Sur une machine lente, cela peut prendre des secondes.

Dans de nombreux contextes de serveur, le temps de dĂ©marrage est pratiquement insignifiant. Un serveur Node.js ayant besoin d'informations sur tous les ports est probablement en fait « plus performant » s’il charge toutes les informations requises en mĂ©moire chaque fois que le serveur dĂ©marre et pourra ainsi traiter les requĂȘtes plus rapidement. Le module abordĂ© dans cet exemple n'est pas un « mauvais » module. Cependant, les applications Electron ne devraient pas avoir Ă  charger, analyser et stocker en mĂ©moire des informations dont elles n'ont pas rĂ©ellement besoin.

En bref, un module apparemment excellent, Ă©crit principalement pour les serveurs Node.js fonctionnant sous Linux peut ĂȘtre une mauvaise nouvelle pour les performances de votre application. Dans cet exemple particulier, la bonne solution Ă©tait de n’utiliser aucun module et d’utiliser Ă  la place les contrĂŽles de connectivitĂ© inclus dans les versions ultĂ©rieures de Chromium.

Comment ?​

Lorsque vous envisagez d'utiliser un module, nous vous recommandons de vérifier :

  1. La taille des dépendances incluses
  2. Les ressources nécessaires pour le charger (require())
  3. Les ressources requises pour effectuer l’action qui vous intĂ©resse

La gĂ©nĂ©ration d’un profil CPU et d’un profil de mĂ©moire de tas pour le chargement d’un module peut ĂȘtre effectuĂ©e avec une seule commande sur la ligne de commande. Dans l’exemple ci-dessous, nous examinons le module populaire request.

node --cpu-prof --heap-prof -e "require('request')"

L’exĂ©cution de cette commande aboutit Ă  un fichier .cpuprofile et un fichier .heapprofile dans le rĂ©pertoire oĂč vous l’avez exĂ©cutĂ©. Les deux fichiers peuvent ĂȘtre analysĂ©s en dĂ©tails Ă  l’aide des outils de dĂ©veloppement de Chrome et en utilisant respectivement les onglets Performance et Memory.

Profil de performance de CPU

Profil de performance de la mémoire du tas

Dans cet exemple et sur la machine de l'auteur, nous avons observé que le chargement de request prenait prÚs d'une demi-seconde, alors que node-fetch prenait lui, moins de 50ms et utilisait énormément moins de mémoire.

2. Ne pas charger ni exĂ©cuter du code plus tĂŽt que nĂ©cessaire​

Si vous avez des opĂ©rations de configuration coĂ»teuses, envisagez de les reporter. Inspectez tout ce qui est exĂ©cutĂ© juste aprĂšs le dĂ©marrage de l’application. Au lieu de dĂ©clencher toutes les opĂ©rations immĂ©diatement, envisagez de les Ă©chelonner dans une sĂ©quence plus en rapport avec le parcours de l’utilisateur.

Dans un dĂ©veloppement traditionnel avec Node.js, nous avons l’habitude de mettre toutes nos dĂ©clarations require() en haut. Si vous Ă©crivez actuellement votre application Electron de cette façon et que vous utilisez des modules de taille importante dont vous n’avez pas immĂ©diatement besoin, appliquez la stratĂ©gie prĂ©cĂ©dante et reportez leur chargement Ă  un moment plus opportun.

Pourquoi ?​

Le chargement des modules est une opération étonnamment coûteuse, en particulier sur Windows. Lorsque votre application démarre, elle ne doit pas obliger les utilisateurs à attendre des opérations qui ne sont pas nécessaires à ce moment précis.

Cela peut sembler Ă©vident, mais de nombreuses applications ont tendance Ă  effectuer une grande quantitĂ© de travail immĂ©diatement aprĂšs le lancement de l’application - comme la vĂ©rification des mises Ă  jour, le tĂ©lĂ©chargement de contenu utilisĂ© dans un flux ultĂ©rieur ou l’exĂ©cution d’opĂ©rations d’E/S disque lourdes .

Prenons Visual Studio Code comme exemple. Lorsque vous ouvrez un fichier, il affiche immédiatement le fichier sans aucune mise en surbrillance de code, priorisant ainsi votre capacité à interagir avec le texte. Une fois ce travail effectué, il passera alors à la mise en surbrillance du code.

Comment ?​

Nous allons pour exemple supposer que votre application analyse des fichiers au format fictif .foo . Pour ce faire, elle s’appuie sur le module foo-parser tout aussi fictif . Dans un dĂ©veloppement Node.js traditionnel, vous ecririez du code chargeant les dĂ©pendances sans attendre:

parser.js
const fs = require('node:fs')

const fooParser = require('foo-parser')

class Parser {
constructor () {
this.files = fs.readdirSync('.')
}

getParsedFiles () {
return fooParser.parse(this.files)
}
}

const parser = new Parser()

module.exports = { parser }

Dans l’exemple ci-dessus, nous effectuons beaucoup de travail exĂ©cutĂ© dĂšs que que le fichier est chargĂ©. Mais avons-nous besoin d’obtenir les fichiers analysĂ©s tout de suite? Ne pourrions-nous pas faire ce travail un peu plus tard, lorsque getParsedFiles() est rĂ©ellement appelĂ©?

parser.js
// "fs" est probablement déja chargé, donc l'appel `require()` est peu coûteux
const fs = require('node:fs')

class Parser {
async getFiles () {
// Touchez le disque dÚs que `getFiles` est appelé, pas avant.
//Assurez-vous Ă©galement que nous ne bloquons pas d’autres opĂ©rations en utilisant
// la version asynchrone.
this.files = this.files || await fs.promises.readdir('.')

retourne ceci. iles
}

async getParsedFiles () {
// Notre outil fictif foo-parser est un module conséquent et coûteux à charger, donc
// diffÚrez cette tùche jusqu'à ce que nous ayons réellement besoin d'analyser les fichiers.
Étant donnĂ© que 'require()' est livrĂ© avec un cache de module, l’appel Ă  'require()'
// ne sera coĂ»teux qu’une seule fois - les appels ultĂ©rieurs de 'getParsedFiles()'
// seront plus rapides.
const fooParser = require('foo-parser')
const files = await this.getFiles()

return fooParser.parse(files)
}
}

// Cette opération est maintenant beaucoup plus légÚre que dans l'exemple précédant
const parser = new Parser()

module.exports = { parser }

En bref, allouez les ressources en « juste à temps » plutÎt que de les allouer toutes au démarrage de votre application.

3. Eviter de bloquer le processus principal​

Le processus principal d’Electron (parfois appelĂ© « processus du navigateur Â») est spĂ©cial: il est le processus parent de tous les autres processus de votre application et est le processus de base avec lequel le systĂšme d’exploitation interagit. Il gĂšre les fenĂȘtres, les interactions et la communication entre diffĂ©rents composants de votre application. Il hĂ©berge Ă©galement le thread d’interface utilisateur .

Vous ne devez en aucun cas bloquer ce processus et le thread d’interface utilisateur par des opĂ©rations de longue durĂ©e. Le blocage du thread d’interface utilisateur signifie que l’ensemble de votre application se figera jusqu’à ce que le processus principal soit prĂȘt Ă  poursuivre le traitement.

Pourquoi ?​

Le processus principal et son thread d’interface utilisateur sont essentiellement la tour de contrĂŽle des principales opĂ©rations Ă  l’intĂ©rieur de votre application. Lorsque le systĂšme d’exploitation informe votre application d’un clic de souris, il passe par le processus principal avant d’atteindre votre fenĂȘtre. Si votre fenĂȘtre rend une animation fluide, elle devra Ă©changer avec le processus GPU - une fois de plus en passant par le processus principal.

Electron et Chromium prennent soin d'attribuer des nouveaux threads aux E/S de disque lourdes et aux opĂ©rations liĂ©es au processeur afin d'Ă©viter de bloquer le thread d’interface utilisateur. Vous devez en faire de mĂȘme.

Comment ?​

La puissante architecture multi-processus d’Electron est lĂ  pour vous aider en ce qui concerne les tĂąches de longue durĂ©e, mais comprend Ă©galement quelques piĂšges cotĂ© performances.

  1. Pour les tùches de longue durée lourdes pour le CPU , utilisez soit des threads de worker, ou envisagez de les déplacer vers la BrowserWindow ou (en dernier recours) génÚrez un processus dédié.

  2. Évitez autant que possible, d’utiliser l’IPC synchrone et le module @electron/remote . Bien qu’il existe des cas d’utilisation lĂ©gitimes, il est beaucoup trop facile, en l'utilisant, de bloquer sans le savoir le thread de l’interface utilisateur.

  3. Évitez d’utiliser des opĂ©rations d’E/S bloquantes dans le processus principal. Bref, chaque fois que des modules de base de Node.js (comme fs ou child_process) offrent une version synchrone ou une version asynchrone vous devez orivilĂ©gier la variante asynchrone et non-bloquante. .

4. Eviter de bloquer les processus de rendu​

Étant donnĂ© qu’Electron est livrĂ© avec une version courante de Chromium, vous pouvez utiliser les fonctionnalitĂ©s les plus rĂ©centes et les plus performantes offertes par la plate-forme Web pour diffĂ©rer ou se dĂ©barasser des opĂ©rations lourdes afin que votre application soit fluide et rĂ©active.

Pourquoi ?​

Votre application a probablement beaucoup de JavaScript Ă  exĂ©cuter dans le processus de rendu. L’astuce consiste Ă  exĂ©cuter les opĂ©rations le plus rapidement possible sans rĂ©duire les ressources nĂ©cessaires au bon fonctionnement en ce qui concerne le dĂ©filement, les animations Ă  60ips et la rĂ©activitĂ© aux saisies de l'utilisateur.

L'orchestration du flux des opérations dans le code de votre moteur de rendu est particuliÚrement utile si les utilisateurs se plaignent que parfois l'application fonctionne par acoups.

Comment ?​

De maniÚre générale, tous les conseils prodigués pour créer des applications web performantes pour les navigateurs modernes s'appliquent également aux moteurs de rendu d'Electron. Les deux principaux outils à votre disposition sont actuellement requestIdleCallback() pour les petites opérations et Web Workers pour les opérations de longue durée.

requestIdleCallback() permet aux dĂ©veloppeurs de mettre en file d’attente une fonction Ă  exĂ©cuter dĂšs que le processus entre dans une pĂ©riode d’inactivitĂ©. Il vous permet d’effectuer un travail de faible prioritĂ© ou en arriĂšre-plan sans affecter l’expĂ©rience utilisateur. Pour plus d’informations sur son utilisation, consultez sa documentation sur MDN.

Les Web Workers sont un outil puissant pour exĂ©cuter du code dans un thread sĂ©parĂ©. There are some caveats to consider – consult Electron's multithreading documentation and the MDN documentation for Web Workers. Ils constituent une solution idĂ©ale pour toute opĂ©ration nĂ©cessitant beaucoup de puissance CPU pendant une longue pĂ©riode.

5. Eviter les polyfills inutiles​

L’un des grands avantages d’Electron est de savoir exactement quel moteur va analyser vos JavaScript, HTML et CSS. Si vous rĂ©utilisez du code qui a Ă©tĂ© Ă©crit pour le Web en gĂ©nĂ©ral, assurez-vous de ne pas coder de polyfill pour des fonctionnalitĂ©s incluses dans Electron.

Pourquoi ?​

Lors de la crĂ©ation d’une application Web pour l’Internet d’aujourd’hui, les environnements les plus anciens dictent les fonctionnalitĂ©s que vous pouvez ou non utiliser. MĂȘme si Electron prend en charge les trĂšs performants filtres et animations CSS, un navigateur plus ancien ne le fera pas. LĂ  oĂč vous pouviez utiliser WebGL, vos dĂ©veloppeurs ont peut-ĂȘtre choisi une solution plus gourmande en ressources pour prendre en charge les tĂ©lĂ©phones plus anciens.

En ce qui concerne JavaScript, vous avez peut-ĂȘtre inclus des bibliothĂšques de boĂźte Ă  outils comme jQuery pour les sĂ©lecteurs DOM ou des polyfills comme le regenerator-runtime pour prendre en charge async/await.

Il est rare qu’un polyfill basĂ© sur JavaScript soit plus rapide que l’équivalent natif d'Electron. Ne ralentissez pas votre application Electron en livrant votre propre version des fonctionnalitĂ©s standard de la plate-forme Web.

Comment ?​

Raisonner en supposant que les polyfills sont inutiles dans les versions en cours d’Electron. If you have doubts, check caniuse.com and check if the version of Chromium used in your Electron version supports the feature you desire.

Examinez, en plus et avec attention, les bibliothÚques que vous utilisez. Sont-elles vraiment nécessaires ? jQuery, par exemple, a eu un tel succÚs que plusieurs de ses fonctionnalités font maintenant partie du jeu de fonctionnalités standards de JavaScript.

Si vous utilisez un transpileur/compilateur comme TypeScript, examinez sa configuration et assurez-vous de cibler la derniùre version d’ECMAScript prise en charge par Electron.

6. Faire la chasse aux requĂȘtes rĂ©seau inutiles ou bloquantes​

Évitez d'aller rĂ©cupĂ©rer des ressources sur Internet si elles ne sont modifiĂ©es que trĂšs rarement et qu'elles peuvent facilement ĂȘtre livrĂ©es avec votre application.

Pourquoi ?​

De nombreux utilisateurs d'Electron commencent avec une application complĂštement web qu'ils transforment en une application de bureau. En tant que dĂ©veloppeurs Web, nous avons l’habitude de charger des ressources Ă  partir d’une variĂ©tĂ© de rĂ©seaux de diffusion de contenu(cdn). Mais, maintenant que vous allez livrer une application de bureau, essayez dans la mesure du possible, de « couper le cordon Â» et Ă©vitez ainsi Ă  vos utilisateurs d'attendre des ressources qui ne changent jamais et qui pourraient facilement ĂȘtre incluses dans votre application.

Les Google Fonts sont un exemple typique. De nombreux développeurs utilisent l'impressionnante collection de polices gratuites de Google, disponible sur cdn. L'utilisation est trÚs simple : On inclus quelques lignes de CSS et Google s'occupera du reste.

Lors de la crĂ©ation d’une application Electron, vos utilisateurs attendront moins si vous tĂ©lĂ©chargez les polices et les regroupez avec votre application.

Comment ?​

Dans un monde idĂ©al, votre application n’aurait pas du tout besoin du rĂ©seau pour fonctionner. Pour y parvenir, vous devez comprendre quelles ressources sont tĂ©lĂ©chargĂ©es par votre application - et connaitre leur taille.

Pour ce faire, ouvrez les outils de dĂ©veloppement. Naviguez vers l'onglet RĂ©seau et cochez l'option DĂ©sactiver le cache. Puis, rechargez votre moteur de rendu. À moins que votre application n’interdise de tels rechargements, vous pouvez gĂ©nĂ©ralement dĂ©clencher un rechargement en appuyant sur Cmd + R ou Ctrl + R lorsque les devTools ont le focus.

Les devTools enregistreront minutieusement toutes les requĂȘtes sur le rĂ©seau. Dans un premier temps, faites le point sur toutes les ressources en cours de tĂ©lĂ©chargement, en se concentrant d'abord sur les plus gros fichiers . Y a-t-il des images, des polices ou des fichiers multimĂ©dias qui ne changent pas et pourraient ĂȘtre inclus dans votre bundle ? Si c’est le cas, incluez-les.

Il vous faut ensuite activer la Limitation du RĂ©seau. Recherchez dans la liste dĂ©roulante qui affiche par dĂ©faut Aucune limitation et sĂ©lectionnez un prĂ©rĂ©glage plus lent tel que 3G Rapide. Rechargez votre moteur de rendu et observez si votre application attend inutilement certaines ressources. . Dans de nombreux cas, une application attendra qu’une demande rĂ©seau se termine sans avoir rĂ©ellement besoin de la ressource concernĂ©e.

À titre d’astuce, le chargement Ă  partir d’Internet de ressources que vous souhaiterez peut-ĂȘtre modifier est une stratĂ©gie efficace car dans ce cas il ne vous sera pas obligatoire d'expĂ©dier une mise Ă  jour de l’application. Pour un contrĂŽle plus avancĂ© de la façon dont les ressources sont chargĂ©es, envisagez l'utilisation des Service Workers.

7. Regroupez votre code​

Comme nous l'avons dĂ©jĂ  soulignĂ© dans «Ne pas charger ni exĂ©cuter du code plus tĂŽt que nĂ©cessaire», l'appel Ă  require() est une opĂ©ration coĂ»teuse. Il vaut mieux regrouper le code de votre application dans un seul fichier, si vous ĂȘtes en mesure de le faire,.

Pourquoi ?​

Le développement moderne en JavaScript implique généralement de nombreux fichiers et modules. Bien que cela ne pose pas de problÚme pour le développement avec Electron, nous vous recommandons fortement de regrouper tout votre code en un seul fichier pour vous assurer que le temps perdu par l'appel à require() n'interviendra qu'une seule fois lors du chargement de l'application.

Comment ?​

Il existe de nombreux empaqueteurs (bundlers) JavaScript et il est prĂ©fĂ©rable pour ne facher personne de ne pas en recommander un plutĂŽt qu'un autre. Nous vous recommandons toutefois d’utiliser un bundler capable de gĂ©rer l’environnement unique d’Electron qui doit gĂ©rer Ă  la fois les environnements de Node.js et du navigateur.

Au moment de l'écriture de cet article, les choix populaires incluent Webpack, Parcelet rollup.js.

8. Appeler Menu.setApplicationMenu(null) lorsque vous n'avez pas besoin d'un menu par dĂ©faut​

Electron définit un menu par défaut au démarrage avec quelques entrées standards. Mais votre application peut nécessiter d'y apporter des modifications pour différentes raisons et cela améliorera les performances du démarrage.

Pourquoi ?​

Si vous construisez votre propre menu ou utilisez une fenĂȘtre sans cadre sans menu natif, vous devez l'indiquer Ă  Electron suffisamment tĂŽt pour empĂȘcher la configuration du menu par dĂ©faut.

Comment ?​

Il suffit d'appeler Menu.setApplicationMenu(null) avant le app.on("ready"). Cela empĂȘchera Electron de configurer un menu par dĂ©faut. Vous pouvez Ă©galement voir la discussion sur https://github.com/electron/electron/issues/35512 qui s'y rapporte.