Dans le développement Web, à mesure que la demande augmente et que la base de code s'étend, les pages Web que nous publions finalement s'étendent également progressivement. Cependant, cette expansion signifie non seulement occuper plus de bande passante de transmission, mais également que les utilisateurs peuvent avoir une moins bonne expérience de performance lors de leur navigation sur le Web. Une fois que le navigateur a téléchargé le script dont dépend une certaine page, il doit encore passer par ces étapes d'analyse syntaxique, d'interprétation et d'exécution. Cet article procédera à une analyse approfondie du traitement JavaScript par le navigateur, découvrira les coupables qui affectent le temps de démarrage de votre application et proposera des solutions correspondantes basées sur mon expérience personnelle. Avec le recul, nous n'avons pas spécifiquement réfléchi à la façon d'optimiser les étapes d'analyse/compilation JavaScript ; nous nous attendions à ce que l'analyseur termine l'opération d'analyse instantanément après avoir découvert la balise <script>
, mais c'est évidemment une chimère. La figure suivante est un aperçu du principe de fonctionnement du moteur V8 :
Analysons en profondeur les étapes clés.
Pendant la phase de démarrage, l'analyse syntaxique, la compilation et l'exécution du script occupent la majeure partie du temps d'exécution du moteur JavaScript. En d'autres termes, le retard provoqué par ces processus reflétera véritablement le délai d'interaction de l'utilisateur ; par exemple, l'utilisateur a vu un bouton, mais il lui faudra plusieurs secondes avant de pouvoir réellement cliquer dessus, ce qui affectera grandement l'expérience utilisateur.
L'image ci-dessus est le résultat de l'analyse d'un site Web utilisant les statistiques d'appel V8 RunTime intégrées de Chrome Canary. Il convient de noter que l'analyse syntaxique et la compilation dans les navigateurs de bureau prennent up Cela prend encore beaucoup de temps, mais cela prend encore plus de temps sur le terminal mobile. En fait, le temps consacré à l'analyse syntaxique et à la compilation sur les grands sites Web tels que Facebook, Wikipedia et Reddit ne peut être ignoré :
La zone rose dans l'image ci-dessus représente le temps passé sur V8 Par rapport au temps passé en C de Blink, l'orange et le jaune représentent respectivement la proportion de temps d'analyse syntaxique et de compilation. Sebastian Markbage de Facebook et Rob Wormald de Google ont également posté sur Twitter que le long temps d'analyse syntaxique de JavaScript est devenu un problème qui ne peut être ignoré. Ce dernier a également déclaré que c'était également l'une des principales consommations au démarrage d'Angular.
Avec la vague entrante de terminaux mobiles, nous devons faire face à un fait cruel : le processus d'analyse et de compilation du même corps de paquet sur le terminal mobile coûte autant que sur le bureau Le navigateur prend 2 à 5 fois plus de temps. Bien sûr, les téléphones haut de gamme comme l'iPhone ou le Pixel fonctionneront bien mieux que les téléphones de milieu de gamme comme le Moto G4. Cela nous rappelle que lors des tests, nous ne devons pas seulement utiliser les téléphones haut de gamme qui nous entourent, mais prendre en compte les deux. gamme et téléphones bas de gamme :
L'image ci-dessus est une comparaison du temps d'analyse d'un corps de package JavaScript de 1 Mo entre certains navigateurs de bureau et navigateurs mobiles. les différences entre les téléphones mobiles avec des configurations différentes peuvent être trouvées d'énormes différences. Lorsque le corps de notre package d'application est déjà très volumineux, l'utilisation de certaines techniques d'empaquetage modernes, telles que le fractionnement de code, TreeShaking, la mise en cache Service Workder, etc., aura un impact important sur le temps de démarrage. D'un autre point de vue, même s'il s'agit d'un petit module, si votre code est mal écrit ou si vous utilisez de mauvaises bibliothèques de dépendances, votre thread principal passera beaucoup de temps en compilation ou en appels de fonctions redondants. Nous devons clairement comprendre l’importance d’une évaluation complète pour déceler les véritables goulots d’étranglement en matière de performance.
J'ai entendu quelqu'un dire plus d'une fois, je ne suis pas Facebook. Quel impact l'analyse et la compilation de la syntaxe JavaScript que vous avez mentionnées
auront-elles sur d'autres sites Web ? J'étais également curieux de connaître ce problème, j'ai donc passé deux mois à analyser plus de 6 000 sites Web ; ces sites Web incluaient des frameworks ou des bibliothèques populaires tels que React, Angular, Ember et Vue. La plupart des tests sont basés sur WebPageTest, vous pouvez donc facilement reproduire ces résultats de tests. Les navigateurs de bureau avec accès fibre prennent environ 8 secondes pour permettre l'interaction de l'utilisateur, tandis que le Moto G4 dans un environnement 3G prend environ 16 secondes pour permettre l'interaction de l'utilisateur.
La plupart des applications passeront environ 4 secondes dans la phase de démarrage de JavaScript (analyse grammaticale, compilation, exécution) dans les navigateurs de bureau :
Dans les navigateurs mobiles, il faut environ 36 % de temps en plus pour analyser la syntaxe :
De plus, les statistiques montrent que tous les sites Web ne proposent pas un énorme package JS aux utilisateurs. La taille moyenne du package compressé Gzip téléchargé par les utilisateurs est de 410 Ko, ce qui est fondamentalement cohérent avec les 420 Ko de données précédemment publiées par HTTPArchive. Mais le pire site Web envoie un script de 10 Mo directement à l'utilisateur, ce qui est tout simplement terrible.
Grâce aux statistiques ci-dessus, nous pouvons constater que le volume du package est important, mais ce n'est pas le seul facteur. Le temps nécessaire à l'analyse syntaxique et à la compilation n'est pas nécessairement le cas. augmenter avec la croissance du volume du colis et la croissance linéaire. De manière générale, un petit package JavaScript se chargera plus rapidement (en ignorant les différences entre les navigateurs, les appareils et les connexions réseau). Cependant, avec la même taille de 200 Ko, le temps d'analyse syntaxique et de compilation des packages des différents développeurs sera énorme. les uns des autres et ne peuvent être comparés.
La chronologie ouverte (panneau de performances) > proportion du temps que le site Web consacre à l’analyse/compilation syntaxique. Si vous souhaitez des informations plus complètes, vous pouvez activer les statistiques d'appel d'exécution du V8. Dans Canary, il se trouve dans Timeline sous Experims > V8 Runtime Call Stats.
Ouvrez la page about:tracing L'outil de suivi sous-jacent fourni par Chrome nous permet d'utiliser disabled-by-default-v8.runtime_stats
pour comprendre en profondeur la consommation de temps. du V8. La V8 fournit également des conseils détaillés sur la façon d'utiliser cette fonctionnalité.
La page de répartition du traitement dans WebPageTest sera automatiquement enregistrée lorsque nous activerons Chrome > Compilation Timeline V8, heures EvaluateScript et FunctionCall. Nous pouvons également activer les statistiques d'appel d'exécution en spécifiant disabled-by-default-v8.runtime_stats
.
Pour plus d'instructions, veuillez vous référer à mon résumé.
Nous pouvons également utiliser l'API User Timing recommandée par Nolan Lawson pour évaluer le temps d'analyse grammaticale. Cependant, cette méthode peut être affectée par le processus de pré-analyse V8. Nous pouvons apprendre de la méthode de Nolan dans l'évaluation optimise-js et ajouter une chaîne aléatoire à la fin du script pour résoudre ce problème. J'utilise une méthode similaire basée sur Google Analytics pour évaluer le temps d'analyse lorsque de vrais utilisateurs et appareils visitent le site Web :
L'outil DeviceTiming d'Etsy peut simuler un certain Évaluer l'analyse syntaxique et le temps d'exécution de la page dans certains environnements restreints. Il enveloppe les scripts locaux dans le code d'un outil d'instrumentation afin que notre page puisse simuler l'accès à partir de différents appareils. Vous pouvez lire l'article Benchmarking JS Parsing and Execution on Mobile Devices de Daniel Espeset pour en savoir plus sur son utilisation.
Réduisez la taille du corps du package JavaScript. Nous avons également mentionné ci-dessus que des corps de package plus petits signifient souvent moins de charge de travail d'analyse, ce qui peut également réduire la consommation de temps du navigateur dans les phases d'analyse et de compilation.
Utilisez des outils de fractionnement de code pour transmettre du code à la demande et charger paresseusement les modules restants. C'est probablement la meilleure approche, car des modèles comme PRPL encouragent le regroupement basé sur les itinéraires et sont actuellement largement utilisés par Flipkart, Housing.com et Twitter.
Diffusion de scripts : dans le passé, la V8 encourageait les développeurs à utiliser async/defer
pour obtenir une amélioration des performances de 10 à 20 % basée sur la diffusion de scripts. Cette technique permet à l'analyseur HTML d'attribuer les tâches de chargement de script correspondantes à un thread de streaming de script dédié, évitant ainsi de bloquer l'analyse du document. V8 recommande de charger des modules plus gros le plus tôt possible, après tout, nous n'avons qu'un seul thread de streamer.
Évaluer le coût de l'analyse de nos dépendances. Nous devrions faire de notre mieux pour choisir des dépendances qui ont les mêmes fonctionnalités mais se chargent plus rapidement, comme l'utilisation de Preact ou Inferno au lieu de React, qui sont plus petites et nécessitent moins de temps d'analyse syntaxique et de compilation que React. Paul Lewis a également discuté du coût de démarrage du framework dans un article récent, qui coïncide avec la déclaration de Sebastian Markbage : La meilleure façon d'évaluer le coût de démarrage d'un framework est d'abord de restituer une interface, puis de la supprimer et enfin de la restituer. Le premier processus de rendu comprendra l'analyse et la compilation. Grâce à la comparaison, la consommation de démarrage du framework pourra être trouvée.
Si votre framework JavaScript prend en charge le mode de compilation AOT (ahead-of-time), il peut réduire efficacement le temps d'analyse et de compilation. Les applications angulaires bénéficient de ce modèle :
Ne vous découragez pas, vous n'êtes pas le seul à avoir du mal à améliorer le temps de démarrage, notre équipe V8 a également travaillé dur. Nous avons constaté qu'Octane, un outil d'évaluation précédent, est une bonne simulation de scénarios réels. Il est très cohérent avec les habitudes réelles des utilisateurs en termes de micro-framework et de démarrage à froid. Sur la base de ces outils, l'équipe V8 a également obtenu une amélioration des performances de démarrage d'environ 25 % lors de travaux antérieurs :
Dans cette section, nous passerons en revue les outils que nous avons utilisés dans le ces dernières années, les techniques permettant d'améliorer l'analyse syntaxique et le temps de compilation sont expliquées.
Chrome 42 a commencé à introduire le concept de ce qu'on appelle le cache de code, qui nous fournit donc un mécanisme pour stocker des copies de code compilées. , lorsque l'utilisateur visite la page une deuxième fois, les étapes d'exploration, d'analyse et de compilation du script peuvent être évitées. De plus, nous avons également constaté que ce mécanisme peut également éviter environ 40 % du temps de compilation lors de visites répétées. Ici, je vais introduire quelques détails en profondeur :
La mise en cache du code sera destinée aux scripts. exécuté à plusieurs reprises dans les 72 heures de travail.
Pour les scripts dans Service Workers, la mise en cache du code fonctionne également pour les scripts dans les 72 heures.
Pour les scripts mis en cache dans Cache Storage à l'aide d'un Service Worker, la mise en cache du code fonctionne la première fois que le script est exécuté.
En bref, pour le code JavaScript activement mis en cache, il peut ignorer les étapes d'analyse syntaxique et de compilation au maximum au troisième appel. Nous pouvons utiliser chrome://flags/#v8-cache-strategies-for-cache-storage
pour voir la différence, ou nous pouvons configurer js-flags=profile-deserialization
pour exécuter Chrome pour voir si le code est chargé à partir du cache de code. Cependant, il convient de noter que le mécanisme de mise en cache du code ne mettra en cache que le code compilé, qui fait principalement référence au code de niveau supérieur souvent utilisé pour définir des variables globales. Le code compilé paresseux tel que les définitions de fonctions ne sera pas mis en cache, mais IIFE est également inclus dans la V8, donc ces fonctions peuvent également être mises en cache.
Script Streaming permet d'analyser des scripts asynchrones dans un thread en arrière-plan, ce qui peut améliorer le temps de chargement des pages d'environ 10 %. Comme mentionné ci-dessus, ce mécanisme fonctionne également pour les scripts de synchronisation.
Cette fonctionnalité est mentionnée pour la première fois, donc la V8 autorisera tous les scripts, même les scripts bloquants <script src=''>
peuvent être analysés par les threads d'arrière-plan. Cependant, l'inconvénient est qu'il n'existe actuellement qu'un seul thread d'arrière-plan de streaming. Nous vous recommandons donc d'analyser d'abord les scripts volumineux et critiques. En pratique, nous recommandons d'ajouter <script defer>
à l'intérieur du bloc <head>
afin que le moteur du navigateur puisse découvrir le script qui doit être analysé le plus tôt possible, puis l'attribuer à un thread d'arrière-plan pour le traitement. Nous pouvons également vérifier la chronologie de DevTools pour déterminer si le script est analysé en arrière-plan. Surtout lorsque vous avez un script critique qui doit être analysé, vous devez vous assurer que le script est analysé par le thread de streaming.
Nous nous engageons également à créer un analyseur plus léger et plus rapide, qui constitue actuellement le plus gros goulot d'étranglement du thread principal de la V8. réside dans ce qu’on appelle la consommation analytique non linéaire. Par exemple, nous avons le morceau de code suivant :
(function (global, module) { … })(this, function module() { my functions })
V8 ne sait pas si nous avons besoin du module module
lors de la compilation du script principal, nous allons donc abandonner temporairement sa compilation. Et lorsque nous prévoyons de compiler module
, nous devons réanalyser toutes les fonctions internes. C'est la raison de la soi-disant non-linéarité du temps d'analyse V8. Toute fonction à une profondeur de niveau N peut être ré-analysée N fois. La V8 est déjà capable de collecter des informations sur toutes les fonctions internes lors de la première compilation, donc la V8 ignorera toutes les fonctions internes dans les compilations futures. Pour la fonction ci-dessus sous la forme de module
, cela représentera une grande amélioration des performances. Il est recommandé de lire Le(s) analyseur(s) V8 — Conception, défis et analyse JavaScript améliorée pour plus d'informations. La V8 recherche également un mécanisme de déchargement approprié pour garantir que le processus de compilation JavaScript puisse être exécuté dans un thread en arrière-plan au démarrage.
Toutes les quelques années, quelqu'un propose que les moteurs fournissent un mécanisme pour traiter les scripts précompilés. En d'autres termes, les développeurs peuvent utiliser des outils de construction ou d'autres outils côté serveur pour convertir les scripts en bytecode, puis les exécuter directement dans. le navigateur Ces bytecodes suffisent. De mon point de vue personnel, transmettre directement du bytecode signifie un corps de package plus grand, ce qui augmentera inévitablement le temps de chargement et nous devons signer le code pour garantir qu'il peut s'exécuter en toute sécurité ; Notre positionnement actuel pour la V8 est d'éviter autant que possible la ré-analyse interne mentionnée ci-dessus pour améliorer le temps de démarrage, tandis que la pré-compilation apportera des risques supplémentaires. Cependant, nous invitons tout le monde à discuter de ce problème ensemble, bien que la V8 se concentre actuellement sur l'amélioration de l'efficacité de la compilation et la promotion de l'utilisation du code de script de cache Service Worker pour améliorer l'efficacité du démarrage. Nous avons également discuté de la précompilation chez BlinkOn7 avec Facebook et Akamai.
Les moteurs JavaScript comme V8 pré-analyseront la plupart des fonctions du script avant d'effectuer une analyse complète. Cela est principalement dû au fait que la plupart des pages contiennent la fonction JavaScript. exécuté immédiatement.
La précompilation peut améliorer le temps de démarrage en traitant uniquement l'ensemble minimum de fonctions requis par le navigateur pour s'exécuter, mais ce mécanisme réduit en réalité l'efficacité face à IIFE. Même si le moteur espère éviter de prétraiter ces fonctions, il est bien moins efficace que des bibliothèques comme optimise-js. optimise-js traitera le script avant le moteur et insérera des parenthèses pour les fonctions qui sont exécutées immédiatement afin de garantir une exécution plus rapide. Ce type de prétraitement a un très bon effet d'optimisation sur le corps du package généré par Browserify et Webpack, qui contient un grand nombre de petits modules pouvant être exécutés immédiatement. Bien que cette petite astuce ne soit pas celle que V8 souhaite utiliser, le mécanisme d'optimisation correspondant doit être introduit au stade actuel.
Les performances dans la phase de démarrage sont cruciales. Les temps d'analyse, de compilation et d'exécution lents peuvent devenir un goulot d'étranglement pour les performances de votre page Web. Nous devons évaluer le temps passé par la page à ce stade et choisir la manière appropriée pour l'optimiser. Nous continuerons également à travailler sur l’amélioration des performances de démarrage du V8 au mieux de nos capacités !
Ce qui précède est le contenu de l'analyse et des solutions des goulots d'étranglement des performances de démarrage JavaScript. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (m.sbmmt.com) !