Analyse de la charge CPU générée par les composants JavaScript individuels

Parlons un peu de la façon d'observer la quantité de ressources CPU consommées par le code JavaScript de l'application. Dans le même temps, je propose de construire notre conversation autour des composants - les blocs de construction de base de l'application. Avec cette approche, tous les efforts pour améliorer la productivité (ou les efforts pour trouver les causes des ralentissements des programmes) peuvent se concentrer sur (espérons-le) de petits fragments autosuffisants du projet. Dans le même temps, je suppose que votre application frontale, comme de nombreux autres projets modernes, a été créée en assemblant de petits fragments de l'interface adaptés à une utilisation répétée. Si ce n'est pas le cas, alors notre raisonnement peut être appliqué à une autre application, mais vous devrez trouver votre propre façon de diviser votre code à grande échelle en fragments et vous devrez réfléchir à la façon d'analyser ces fragments.



Pourquoi est-ce nécessaire?


Pourquoi mesurer la consommation CPU par JavaScript? Le fait est que de nos jours, les performances des applications sont le plus souvent liées aux capacités du processeur. Permettez-moi de citer librement les paroles de Steve Soders et Pat Minan d'une interview que j'ai prise pour Planet Performance Podcast . Tous deux ont déclaré que les performances des applications ne se limitaient plus aux capacités réseau ou aux latences réseau. Les réseaux deviennent de plus en plus rapides. Les développeurs ont en outre appris à compresser les réponses textuelles du serveur à l'aide de GZIP (ou plutôt à l'aide de brotli) et ont compris comment optimiser les images. C'est très simple.

Le goulot d'étranglement des performances des applications modernes est les processeurs. Cela est particulièrement vrai dans l'environnement mobile. Et dans le même temps, nos attentes concernant les capacités interactives des applications Web modernes ont augmenté. Nous nous attendons à ce que les interfaces de ces applications fonctionnent très rapidement et en douceur. Et tout cela nécessite de plus en plus de code JavaScript. De plus, nous devons nous rappeler que 1 Mo d'images n'est pas la même chose que 1 Mo de JavaScript. Les images sont téléchargées progressivement et l'application résout actuellement d'autres problèmes. Mais le code JavaScript est souvent une telle ressource, sans laquelle l'application s'avère inopérante. Pour garantir le fonctionnement d'une application moderne, de grandes quantités de code JS sont nécessaires, qui, avant de vraiment fonctionner, doivent être analysées et exécutées. Et ce sont des tâches qui dépendent fortement des capacités du processeur.

Indicateur de performance


Nous utiliserons un tel indicateur de la vitesse des fragments de code comme le nombre d'instructions du processeur nécessaires pour les traiter. Cela nous permettra de séparer les mesures des propriétés d'un ordinateur particulier et de l'état dans lequel il se trouve au moment de la mesure. Les mesures basées sur le temps (comme TTI) ont trop de «bruit». Ils dépendent de l'état de la connexion réseau, ainsi que de tout ce qui se passe sur l'ordinateur au moment de la mesure. Par exemple, certains scripts exécutés lors du chargement de la page examinée, ou des virus qui sont occupés par quelque chose dans les processus d'arrière-plan, peuvent affecter les indicateurs de performance temporels. La même chose peut être dite à propos des extensions de navigateur, qui peuvent consommer beaucoup de ressources système et ralentir la page. En revanche, lors du calcul du nombre d'instructions du processeur, le temps n'a pas d'importance. Ces indicateurs peuvent être, comme vous le verrez bientôt, vraiment stables.

Idée


Voici l'idée qui sous-tend notre travail: nous devons créer un «laboratoire» dans lequel le code sera lancé et examiné lorsque des modifications y seront apportées. Par «laboratoire», je veux dire un ordinateur ordinaire, peut-être celui que vous utilisez constamment. Les systèmes de contrôle de version nous donnent à notre disposition des crochets avec lesquels vous pouvez intercepter certains événements et effectuer certaines vérifications. Bien entendu, les mesures en «laboratoire» peuvent être effectuées après validation. Mais vous savez probablement que les modifications apportées au code qui a atteint l'étape de validation seront apportées plus lentement qu'au code en cours d'écriture (le cas échéant). La même chose s'applique à la correction du code bêta du produit et à la correction du code entré en production.

Nous avons besoin à chaque fois que le code est modifié, que ses performances soient comparées avant et après les modifications. Ce faisant, nous nous efforçons d'étudier les composants de manière isolée. Nous pourrons ainsi voir clairement les problèmes et savoir exactement où ils se posent.

La bonne chose est que de telles études peuvent être effectuées dans un vrai navigateur, en utilisant, par exemple, Puppeteer. Il s'agit d'un outil qui vous permet de contrôler le navigateur sans interface utilisateur à partir de Node.js.

Code de recherche pour la recherche


Afin de trouver le code de l'étude, nous pouvons nous référer à n'importe quel guide de style, ou à n'importe quel système de conception. En général, nous sommes satisfaits de tout ce qui fournit des exemples brefs et isolés d'utilisation de composants.

Qu'est-ce qu'un «guide de style»? Il s'agit généralement d'une application Web qui montre tous les composants ou «blocs de construction» des éléments de l'interface utilisateur disponibles pour le développeur. Il peut s'agir d'une certaine bibliothèque de composants tiers ou de quelque chose créé par vos propres efforts.

En recherchant de tels projets sur Internet, je suis tombé sur un récent fil de discussion sur Twitter qui parlait de bibliothèques relativement nouvelles de composants React. J'ai regardé plusieurs des bibliothèques mentionnées ici.

Sans surprise, les bibliothèques modernes de haute qualité sont fournies avec une documentation qui inclut des exemples de code de travail. Voici quelques bibliothèques et composants Button implémentés par leurs moyens. La documentation de ces bibliothèques contient des exemples d'utilisation de ces composants. Nous parlons de la bibliothèque Chakra et de la bibliothèque Semantic UI React.


Documentation des composants des boutons du chakra


Bouton Documentation sémantique de l'interface utilisateur React

C'est exactement ce dont nous avons besoin. Ce sont des exemples dont nous pouvons examiner le code pour leur consommation de ressources processeur. Des exemples similaires peuvent être trouvés dans les entrailles de la documentation, ou dans les commentaires de code écrits dans le style JSDoc. Si vous avez de la chance, vous trouverez peut-être de tels exemples, conçus sous forme de fichiers séparés, par exemple sous la forme de fichiers de tests unitaires. Il en sera sûrement ainsi. Après tout, nous écrivons tous des tests unitaires. Non?

Fichiers


Imaginez, afin de démontrer la méthode d'analyse des performances décrite, qu'il existe un composant Button dans la bibliothèque que nous étudions, dont le code se trouve dans le fichier Button.js . Le fichier avec le test unitaire Button-test.js est attaché à ce fichier, ainsi qu'un fichier avec un exemple d'utilisation du composant - Button-example.js . Nous devons créer une sorte de page de test, dans l'environnement dont le code de test peut être exécuté. Quelque chose comme test.html .

Composant


Voici un simple composant Button . J'utilise React ici, mais vos composants peuvent être écrits en utilisant n'importe quelle technologie qui vous convient.

 import React from 'react'; const Button = (props) =>  props.href    ? <a {...props} className="Button"/>    : <button {...props} className="Button"/> export default Button; 

Exemple


Et voici un exemple d'utilisation du composant Button . Comme vous pouvez le voir, dans ce cas, il existe deux options de composants qui utilisent des propriétés différentes.

 import React from 'react'; import Button from 'Button'; export default [  <Button onClick={() => alert('ouch')}>    Click me  </Button>,  <Button href="https://reactjs.com">    Follow me  </Button>, ] 

Test


Voici la page test.html qui peut charger tous les composants. Notez les appels de méthode à l'objet de performance . C'est avec leur aide que nous, à notre demande, écrivons dans le fichier journal des performances de Chrome. Très bientôt, nous utiliserons ces enregistrements.

 const examples =  await import(location.hash + '-example.js'); examples.forEach(example =>  performance.mark('my mark start');  ReactDOM.render(<div>{example}</div>, where);  performance.mark('my mark end');  performance.measure(    'my mark', 'my mark start', 'my mark end'); ); 

Runner de test


Afin de charger une page de test dans Chrome, nous pouvons utiliser la bibliothèque Puppeteer Node.js, qui nous donne accès à l'API pour gérer le navigateur. Vous pouvez utiliser cette bibliothèque sur n'importe quel système d'exploitation. Il a sa propre copie de Chrome, mais il peut également être utilisé pour fonctionner avec une instance de Chrome ou de Chrome de différentes versions déjà existantes sur l'ordinateur du développeur. Chrome peut être lancé pour que sa fenêtre soit invisible. Les tests sont effectués automatiquement, tandis que le développeur n'a pas besoin de voir la fenêtre du navigateur. Chrome peut être lancé en mode normal. Ceci est utile à des fins de débogage.

Voici un exemple de script Node.js exécuté à partir de la ligne de commande qui charge une page de test et écrit des données dans un fichier journal de performances. Tout ce qui se passe dans le navigateur entre les commandes tracing.start() et end() est écrit (je veux le noter en détail) dans le fichier trace.json .

 import pup from 'puppeteer'; const browser = await pup.launch(); const page = await browser.newPage(); await page.tracing.start({path: 'trace.json'}); await page.goto('test.html#Button'); await page.tracing.stop(); await browser.close(); 

Le développeur peut gérer le «détail» des données de performance en spécifiant les «catégories» de traçage. Vous pouvez voir la liste des catégories disponibles si vous allez dans Chrome sur chrome://tracing , cliquez sur Record et ouvrez la section Edit categories dans la fenêtre qui apparaît.


Configuration de la composition des données écrites dans le journal des performances

Résultats


Après avoir examiné la page de test à l'aide de Puppeteer, vous pouvez analyser les résultats des mesures de performances en accédant au navigateur à l' trace.json chrome://tracing et en téléchargeant le fichier trace.json vient d'être enregistré.


Visualisation Trace.json

Ici, vous pouvez voir les résultats de l'appel de la méthode performance.measure('my mark') . L'appel de measure() est uniquement à des fins de débogage, au cas où le développeur souhaite ouvrir le fichier trace.json et le voir. Tout ce qui s'est passé avec la page est enfermé dans le bloc my mark .

Voici un trace.json :


Fragment du fichier trace.json

Pour savoir ce dont nous avons besoin, il suffit de soustraire l'indicateur du nombre d'instructions du processeur ( ticount ) du marqueur de Start du même indicateur du marqueur de End . Cela vous permet de savoir combien d'instructions de processeur sont nécessaires pour afficher le composant dans le navigateur. Il s'agit du même nombre que vous pouvez utiliser pour savoir si un composant est devenu plus rapide ou plus lent.

Le diable est dans les détails


Nous avons maintenant mesuré uniquement les indicateurs caractérisant la première sortie sur la page d'un seul composant. Et rien de plus. Il est impératif de mesurer les indicateurs liés à la plus petite quantité de code pouvant être exécutée. Cela vous permet de réduire le niveau de "bruit". Le diable est dans les détails. Plus les performances sont mesurées, mieux c'est. Après les mesures, il est nécessaire d'éliminer des résultats obtenus ce qui est au-delà de l'influence du développeur. Par exemple, les données liées aux opérations de récupération de place. Le composant ne contrôle pas ces opérations. S'ils sont exécutés, cela signifie que le navigateur, en train de rendre le composant, a décidé de les lancer lui-même. Par conséquent, les ressources du processeur qui sont allées à la récupération de place doivent être supprimées des résultats finaux.

Le bloc de données lié à la récupération de place (ce «bloc de données» est plus correctement appelé un «événement») est appelé V8.GCScavenger . Sa tidelta doit être soustraite du nombre d'instructions du processeur qui entrent dans le rendu du composant. Voici la documentation des événements de trace. Certes, il est obsolète et ne contient pas d'informations sur les indicateurs dont nous avons besoin:

  • tidelta - le nombre d'instructions de processeur requises pour traiter un événement.
  • ticount - le nombre d'instructions pour démarrer l'événement.

Vous devez faire très attention à ce que nous mesurons. Les navigateurs sont des entités hautement intelligentes. Ils optimisent le code qui s'exécute plus d'une fois. Dans le graphique suivant, vous pouvez voir le nombre d'instructions de processeur nécessaires pour sortir un certain composant. La première opération de rendu nécessite le plus de ressources. Les opérations suivantes créent une charge beaucoup plus faible sur le processeur. Il convient de garder cela à l'esprit lors de l'analyse des performances du code.


10 opérations de rendu du même composant

Voici un autre détail: si le composant effectue des opérations asynchrones (par exemple, il utilise setTimeout() ou fetch() ), alors la charge sur le système créée par le code asynchrone n'est pas prise en compte. C'est peut-être bien. C'est peut-être mauvais. Si vous étudiez les performances de ces composants, envisagez une étude distincte du code asynchrone.

Signal fort


Si vous adoptez une approche responsable pour résoudre le problème de ce qui est exactement mesuré, vous pouvez obtenir un signal vraiment stable qui reflète l'impact sur les performances de tout changement. J'adore la finesse des lignes dans le graphique suivant.


Résultats de mesure stables

Le graphique du bas montre les résultats de mesure de 10 opérations de rendu d'un élément <span> simple dans React. Rien d'autre n'est inclus dans ces résultats. Il s'avère que cette opération nécessite de 2,15 à 2,2 millions d'instructions de processeur. Si vous encapsulez <span> dans la <p> , pour générer une telle conception, vous avez besoin d'environ 2,3 millions d'instructions. Ce niveau de précision me frappe. Si un développeur peut voir la différence de performances qui apparaît lorsqu'un seul élément <p> est ajouté à une page, cela signifie que le développeur a un outil vraiment puissant entre ses mains.

La manière exacte de représenter les mesures d'une telle précision dépend du développeur. S'il n'a pas besoin d'une telle précision, il peut toujours mesurer les performances de rendu de fragments plus gros.

Informations supplémentaires sur les performances


Maintenant que le développeur a à sa disposition un système de recherche d'indicateurs numériques qui caractérisent très précisément les performances des plus petits fragments de code, le développeur peut utiliser ce système pour résoudre divers problèmes. Ainsi, en utilisant performance.mark() vous pouvez écrire des informations utiles supplémentaires sur trace.json . Vous pouvez dire aux membres de l'équipe de développement ce qui se passe et ce qui provoque une augmentation du nombre d'instructions de processeur nécessaires pour exécuter du code. Vous pouvez inclure dans les rapports de performances des informations sur le nombre de nœuds DOM ou sur le nombre d'opérations d'écriture dans le DOM effectuées par React. En fait, ici, vous pouvez afficher des informations sur un lot. Vous pouvez compter le nombre de recalculs de mise en page. En utilisant Puppeteer, vous pouvez prendre des captures d'écran des pages et comparer l'apparence de l'interface avant et après avoir apporté des modifications. Parfois, l'augmentation du nombre d'instructions du processeur nécessaires pour afficher une page ne semble pas du tout surprenante. Par exemple, si 10 boutons et 12 champs pour l'édition et la mise en forme du texte sont ajoutés à la nouvelle version de la page.

Résumé


Est-il possible pour tout le monde qui a été discuté ici de l'utiliser aujourd'hui? Oui tu peux. Pour ce faire, vous avez besoin de Chrome version 78 ou supérieure. Si trace.json a des tidelta ticount et tidelta , alors ce qui précède est à votre disposition. Les versions antérieures de Chrome ne le font pas.

Malheureusement, les informations sur le nombre d'instructions du processeur ne peuvent pas être obtenues sur la plate-forme Mac. Je n'ai pas encore essayé Windows, donc je ne peux rien dire de précis sur cet OS. En général - nos amis sont Unix et Linux.

Il convient de noter que pour que le navigateur puisse fournir des informations sur les instructions du processeur, vous devrez utiliser quelques indicateurs - ce sont --no-sandbox et --enable-thread-instruction-count . Voici comment les transmettre à un navigateur lancé par Puppeteer:

 await puppeteer.launch({  args: [    '--no-sandbox',    '--enable-thread-instruction-count',  ]}); 

Avec un peu de chance, vous pouvez maintenant faire passer l'analyse des performances de votre application Web au niveau supérieur.

Chers lecteurs! Envisagez-vous d'utiliser la méthodologie d'analyse des performances des projets Web présentée ici?


Source: https://habr.com/ru/post/fr479266/


All Articles