garbage.collect ()

Pour exécuter JavaScript, le navigateur a besoin d'un peu de mémoire, mais quelque part vous devez stocker des objets, des primitives, des fonctions qui sont créées pour toutes les actions de l'utilisateur. Par conséquent, le navigateur alloue d'abord la quantité de RAM requise, et lorsque les objets ne sont pas utilisés, il les nettoie indépendamment.

En théorie, ça sonne bien. En pratique, l'utilisateur ouvre 20 onglets sur YouTube, les réseaux sociaux, lit quelque chose, fonctionne, le navigateur mange de la mémoire, comme Hummer H2 - essence. Le garbage collector, comme ce monstre avec une vadrouille, parcourt toute la mémoire et ajoute de la confusion, tout ralentit et se bloque.



Pour éviter que de telles situations ne se produisent et que les performances de nos sites et applications ne souffrent pas, le développeur front-end devrait savoir comment les ordures affectent les applications, comment le navigateur les collecte et optimise le travail avec la mémoire, et comment tout cela diffère de la dure réalité. Ce n'est que le rapport d' Andrei Roenko ( flapenguin ) à Frontend Conf 2018 .

Nous utilisons le ramasse-miettes (pas à la maison - dans le développement frontal), mais nous ne pensons pas vraiment à ce que c'est, à ce qu'il nous en coûte et à ses opportunités et limitations.

Si le garbage collection fonctionnait vraiment en JavaScript, la plupart des modules npm se supprimeraient immédiatement après l'installation.

Mais bien que ce ne soit pas le cas, et nous parlerons de ce qui est - de l'assemblage d'objets inutiles.


À propos du conférencier : Andrei Roenko a développé l'API Yandex.Map , a été dans le frontend depuis six ans maintenant, il aime créer ses propres abstractions élevées et descendre au sol d'étrangers.

Pourquoi avez-vous besoin d'une collecte des ordures?


Prenons l'exemple de Yandex.Maps. Yandex.Maps est un service énorme et complexe qui utilise beaucoup de JS et presque toutes les API de navigateur existantes, à l'exception des multimédias, et le temps de session moyen est de 5-10 minutes. L'abondance de JavaScript crée de nombreux objets. Faire glisser la carte, ajouter des organisations, des résultats de recherche et de nombreux autres événements se produisant à chaque seconde crée une avalanche d'objets. Ajoutez à cela React et les objets deviennent encore plus.

Cependant, les objets JS n'occupent que 30 à 40 Mo sur la carte. Pour les longues sessions Yandex.Maps et l'allocation constante de nouveaux objets, cela ne suffit pas.

La raison du petit volume d'objets est qu'ils sont correctement collectés par le garbage collector et la mémoire est réutilisée.

Aujourd'hui, nous allons parler de la collecte des ordures sur quatre côtés:

  • Théorie Commençons par elle à parler la même langue et à se comprendre.
  • Une dure réalité. En fin de compte, l'ordinateur exécute du code machine dans lequel toutes les abstractions ne nous sont pas familières. Essayons de comprendre comment fonctionne la collecte des ordures à un niveau bas.
  • Réalité du navigateur. Voyons comment la récupération de place est implémentée dans les moteurs et navigateurs modernes, et quelles conclusions nous pouvons en tirer.
  • Vie quotidienne - parlons de l'application pratique des connaissances acquises dans la vie quotidienne.

Nous soutenons toutes les déclarations avec des exemples de la façon dont vous pouvez et comment vous n'avez pas besoin de le faire.

Pourquoi savoir tout ça?


La collecte des ordures est une chose invisible pour nous, cependant, sachant comment elle est organisée, vous:

  • Ayez une idée de l'outil que vous utilisez, qui est utile dans votre travail.
  • Comprendre où optimiser les applications déjà publiées et comment concevoir les futures pour qu'elles fonctionnent mieux et plus rapidement.
  • Sachez ne pas commettre d'erreurs courantes et ne perdez pas de ressources sur des «optimisations» inutiles et nuisibles.

Théorie


Joel Spolsky a dit un jour:

Toutes les abstractions non triviales fuient.

Le garbage collector est une grande abstraction non triviale qui est corrigée de tous les côtés. Heureusement, il coule très rarement.

Commençons par une théorie, mais sans définitions ennuyeuses. Analysons le travail du collecteur en utilisant du code simple comme exemple:

window.Foo = class Foo { constructor() { this.x = { y: 'y' }; } work(name) { let z = 'z'; return function () { console.log(name, this.xy, z); this.x = null; }.bind(this); } }; 

  • Il y a une classe dans le code.
  • La classe a un constructeur .
  • La méthode de travail renvoie une fonction associée.
  • À l'intérieur de la fonction, cette variable et quelques variables de la fermeture sont utilisées.

Voyons comment ce code se comportera si nous l'exécutons de cette façon:

 var foo = new Foo(); //C   window.worker = foo.work('Brendan Eich'); //     bind,   window.foo = null; //   window.Foo = null; //  ,   -  window.worker(); window.worker = null; //   ,   

Analysons le code et ses composants plus en détail et commençons par la classe.

Déclaration de classe




Nous pouvons supposer que les classes dans ECMAScript 2015 ne sont que du sucre syntaxique pour les fonctions. Toutes les fonctions ont:

  • Fonction. [[Prototype]] est le véritable prototype de la fonction.
  • Foo.prototype est un prototype pour des objets fraîchement créés.
  • Foo.prototype a un lien vers le constructeur via le champ constructeur. Il s'agit d'un objet, il hérite donc de Object.prototype .
  • La méthode de travail est une fonction distincte à laquelle il existe un lien, similaire au constructeur, car ce ne sont que des fonctions. Il peut également définir un prototype et l'appeler via new, mais personne n'utilise rarement ce comportement.

Les prototypes occupent beaucoup d'espace sur le circuit, alors rappelons-le qu'ils le sont, mais les supprimeront pour plus de simplicité.

Création d'un objet de classe




  • Nous mettons notre classe en fenêtre, car les classes n'y arrivent pas par défaut.
  • Créez un objet de classe.
  • La création d'un objet expose automatiquement le prototype de l'objet de classe dans Foo.prototype. Par conséquent, lorsque vous essayez d'appeler la méthode de travail sur un objet, il saura de quel type de travail il s'agit.
  • Notre constructeur crée le champ x dans l'objet à partir de l'objet avec la chaîne.

Voici ce qui s'est passé:



La méthode retourne une fonction liée - il s'agit d'un objet spécial "magique" dans JS, qui se compose d'un ceci lié et d'une fonction qui doit être appelée. La fonction associée a également un prototype et un autre prototype, mais nous nous intéressons à la fermeture. Par spécification, la fermeture est stockée dans l'environnement. Vous connaissez probablement le mot Scope, mais dans les spécifications, le champ est appelé Environnement .



L'environnement stocke une référence à LexicalEnvironment. Il s'agit d'un objet complexe, plus compliqué que sur une diapositive; il stocke des liens vers tout ce qui est accessible à partir d'une fonction. Par exemple, window, Foo, name et z. Il stocke également des liens vers ce que vous n'utilisez pas explicitement. Par exemple, vous pouvez utiliser eval et utiliser accidentellement des objets inutilisés, mais JS ne doit pas se casser.

Donc, nous avons construit tous les objets et maintenant nous allons tout détruire.

Supprimer le lien vers l'objet


Commençons par supprimer le lien vers l'objet, ce lien dans le diagramme est surligné en rouge.



Nous supprimons et rien ne se passe, car de la fenêtre à l'objet, il y a un chemin à travers la fonction liée .



Cela nous pousse à une erreur typique.

Erreur courante - abonnement oublié


 externalElement.addEventListener('click', () => { if (this.shouldDoSomethingOnClick) { this.doSomething(); } }) 

Se produit lorsque vous vous abonnez: en l'utilisant , explicitement via les fonctions de liaison ou de flèche; utiliser quelque chose dans la fermeture. Ensuite, vous oubliez de vous désabonner et la durée de vie de votre objet ou de ce qui se trouve dans le circuit devient la même que la durée de vie d'un abonnement. Par exemple, s'il s'agit d'un élément DOM que vous ne touchez pas, il s'agit très probablement du délai jusqu'à la fin de la vie de la page.

Pour résoudre ces problèmes:

  • Se désinscrire.
  • Réfléchissez à la durée de vie de l'abonnement et à qui en est propriétaire.
  • Si pour une raison quelconque vous ne pouvez pas vous désinscrire, alors annulez les liens (que ce soit = ​​null), ou nettoyez tous les champs de l'objet. Si votre objet fuit, il sera petit et ce n'est pas dommage.
  • Utilisez WeakMap, peut-être que cela vous aidera dans certaines situations.

Supprimer la référence de classe


Allez-y et essayez de supprimer le lien rouge mis en évidence par la classe.



Nous supprimons le lien et rien ne change pour nous. La raison en est que la classe est accessible via BoundThis, dans laquelle il existe un lien vers le prototype, et dans le prototype, il existe un lien vers le constructeur.

Erreur typique du travail inutile


Pourquoi toutes ces démonstrations sont-elles nécessaires? Parce qu'il y a un revers au problème lorsque les gens prennent le conseil d'annuler les liens trop littéralement et d'annuler tout en général.

 destroy() { this._x = null; this._y = null; //  10 this._foobar = null } 

C'est un travail assez inutile. Si l'objet se compose uniquement de références à d'autres objets et qu'il n'y a pas de ressources là-bas, alors destroy () n'est pas nécessaire. Il suffit de perdre la référence à l'objet, et il mourra de lui-même.

Il n'y a pas de conseil universel. Lorsque cela est nécessaire, annulez et, dans le cas contraire, ne l'annulez pas. La remise à zéro n'est pas une erreur, mais simplement un travail inutile.

Allez-y. Appelez la méthode de la fonction liée et elle supprimera le lien entre [object Foo] et [object Object]. Cela entraînera le fait que des objets séparés dans un rectangle bleu apparaissent dans le diagramme.



Ces objets sont des ordures JS. Il va très bien. Cependant, il y a des déchets qui ne peuvent pas être collectés.

Des ordures qui ne vont pas


Dans de nombreuses API de navigateur, vous pouvez créer et détruire un objet. Si l'objet n'est pas détruit, aucun collectionneur ne peut l'assembler.

Objets avec fonctions de création / suppression de paires:

  • createObjectURL (), revokeObjectURL ();
  • WebGL: créer / supprimer un programme / shader / tampon / texture / etc;
  • ImageBitmap.close ();
  • indexDb.close ().

Par exemple, si vous oubliez de supprimer ObjectURL d'une vidéo de 200 Mo, ces 200 Mo resteront en mémoire jusqu'à la fin de la vie de la page et même plus longtemps, car il y a un échange de données entre les onglets. De même dans WebGL, indexDb et d'autres API de navigateur avec des ressources similaires.

Heureusement, dans notre exemple, le rectangle bleu contient uniquement des objets JavaScript, il ne s'agit donc que de déchets qui peuvent être supprimés.

L'étape suivante consiste à effacer le dernier lien de gauche à droite. Il s'agit d'une référence à la méthode que nous avons reçue, une fonction connexe.



Après sa suppression, nous n'aurons plus de liens à gauche et à droite? En fait, il y a encore des liens depuis la fermeture.



Il est important qu'il n'y ait pas de liens de gauche à droite, donc tout sauf la fenêtre est une ordure et elle mourra.

Remarque importante : il y a des références circulaires dans la poubelle, c'est-à-dire des objets qui se réfèrent les uns aux autres. La présence de tels liens n'affecte rien, car le garbage collector ne collecte pas les objets individuels, mais l'ensemble des déchets.



Nous avons regardé les exemples et maintenant à un niveau intuitif nous comprenons ce que sont les ordures, mais donnons une définition complète du concept.

La poubelle est tout ce qui n'est pas un objet vivant.

Tout est devenu très clair. Mais qu'est-ce qu'un objet vivant?

Un objet vivant est un objet qui peut être atteint par des liens depuis l'objet racine.

Deux nouveaux concepts apparaissent: «suivre les liens» et «objet racine». Un objet racine que nous connaissons déjà est la fenêtre, alors commençons par les liens.

Que signifie suivre les liens?


Il existe de nombreux objets liés les uns aux autres et se référant les uns aux autres. Nous allons les onduler, en commençant par l'objet racine.

Nous initialisons la première étape, puis procédons selon l'algorithme suivant: disons que tout sur la crête de la vague est des objets vivants et voyons à quoi ils se réfèrent.



Nous initialisons la première étape. Ensuite, nous agirons selon l'algorithme suivant: disons que tout ce qui est jaune sur la crête de la vague est des objets vivants et voyons à quoi ils se réfèrent.

À quoi ils se réfèrent, nous allons faire une nouvelle crête de la vague:



Terminé et recommencer:

  • Nous revivons.
  • Nous regardons à quoi ils se réfèrent.
  • Créez une nouvelle crête de vague, animez des objets.
  • Nous regardons à quoi ils se réfèrent.



Remarquant qu'une flèche pointe vers un objet déjà vivant, nous ne faisons tout simplement rien. Plus loin selon l'algorithme, jusqu'à épuisement des objets à contourner. Ensuite, nous disons que nous avons trouvé tous les objets vivants, et tout le reste est des ordures.



Ce processus est appelé marquage .

Que signifie l'objet racine?



  • Fenêtre
  • Presque toutes les API de navigateur.
  • Tous promettent.
  • Tout ce qui est mis dans Microtask et Macrotask.
  • Observateurs de mutations, RAF, rappels au ralenti. Tout ce qui peut être atteint à partir de ce qui se trouve dans la RAF ne peut pas être supprimé, car si vous supprimez l'objet utilisé dans la RAF, quelque chose ira probablement mal.

L'assemblage peut avoir lieu à tout moment. Chaque fois que des accolades ou une fonction apparaissent, un nouvel objet est créé. Il n'y a peut-être pas assez de mémoire et le collectionneur ira chercher gratuitement:

 function foo (a, b, c) { function bar (x, y, z) { const x = {}; // nomem, run gc D: // … } while (whatever()) bar(); } 

Dans ce cas, les objets racine seront tout sur la pile des appels. Si, par exemple, vous vous arrêtez à la ligne avec X et supprimez ce à quoi Y fait référence, votre application se bloquera. JS ne nous autorise pas de telles frivolités, vous ne pouvez donc pas supprimer un objet de Y.

Si la partie précédente vous paraissait compliquée, ce sera encore plus difficile.

Une dure réalité


Parlons du monde des machines dans lequel nous traitons le fer, les supports physiques.

La mémoire est un grand tableau dans lequel se trouvent uniquement des nombres, par exemple: nouveau Uint32Array (16 * 2 ** 30).

Créons des objets en mémoire et ajoutons-les de gauche à droite. Nous créons un, deuxième, troisième - ils sont tous de tailles différentes. Nous avons mis des liens le long du chemin.


Au septième objet, la place est terminée, car nous avons 2 places libres, mais nous en avons besoin de 5.

Que peut-on faire ici? La première option consiste à planter. Dans la cour en 2018, tout le monde a les derniers MacBooks et 16 Go de RAM. Il n'y a pas de situations où il n'y a pas de mémoire!

Cependant, laisser les choses suivre leur cours est une mauvaise idée, car sur le Web, cela conduit à un écran similaire:



Ce n'est pas le comportement que nous voulons du programme, mais en général, il est valide. Il existe une catégorie de collectionneurs appelée No-op .

Collecteur sans opération


Avantages:

  • Le collecteur est très simple.
  • Il n'y a tout simplement pas de collecte des ordures.
  • Pas besoin d'écrire ou de penser à la mémoire.

Inconvénients:

  • Tout tombe pour qu'il ne remonte plus.

Pour le frontend, le collecteur no-op n'est pas pertinent, mais est utilisé sur le backend. Par exemple, ayant plusieurs serveurs derrière les équilibreurs, l'application reçoit 32 Go de RAM, puis elle est entièrement détruite. C'est plus simple et les performances ne sont améliorées qu'en redémarrant simplement lorsque la mémoire devient faible.

Sur le Web, c'est impossible et vous devez le nettoyer.

Rechercher et supprimer les ordures


Nous commençons le nettoyage avec des ordures. Nous savons déjà comment procéder. Ordures - objets C et F dans le schéma précédent, car vous ne pouvez pas les atteindre le long des flèches depuis l'objet racine.

Nous prenons ces ordures, les donnons à l'amateur d'ordures et vous avez terminé.



Après le nettoyage, le problème n'est pas résolu, car des trous restent dans la mémoire. Veuillez noter qu'il y a 7 cases gratuites, mais 5 d'entre elles ne peuvent toujours pas être attribuées. Une fragmentation s'est produite et l'assemblage était terminé. Un tel algorithme avec des trous s'appelle Mark et Sweep .

Marquer et balayer


Avantages:

  • Un algorithme très simple. L'un des premiers que vous découvrirez si vous commencez à vous renseigner sur Garbage collector.
  • Cela fonctionne proportionnellement à la quantité de déchets, mais ne fait face que lorsqu'il y a peu de déchets.
  • Si vous n'avez que des objets vivants, alors il ne perd pas de temps et ne fait rien.

Inconvénients:

  • Il faut une logique complexe pour rechercher de l'espace libre, car quand il y a beaucoup de trous dans la mémoire, vous devez essayer un objet dans chacun pour comprendre s'il convient ou non.
  • Fragment la mémoire. Une situation peut se produire avec 200 Mo libres, la mémoire est divisée en petits morceaux et, comme dans l'exemple ci-dessus, il n'y a pas de mémoire solide pour l'objet.

Nous recherchons d'autres idées. Si vous regardez l'image et pensez, la première pensée est de tout déplacer vers la gauche. Ensuite, à droite, il y aura une grande pièce libre dans laquelle notre objet s'insérera calmement.

Il existe un tel algorithme et il s'appelle Mark et Compact .

Marque et compact


Avantages:

  • Mémoire de défragmentation.
  • Il fonctionne proportionnellement au nombre d'objets vivants, ce qui signifie qu'il peut être utilisé lorsqu'il n'y a pratiquement pas de débris.

Inconvénients:

  • Difficile dans le travail et la mise en œuvre.
  • Déplace des objets. Nous avons déplacé l'objet, copié, maintenant il est dans un endroit différent et toute l'opération est assez chère.
  • Il nécessite 2-3 passages dans la mémoire, selon l'implémentation - l'algorithme est lent.

Nous arrivons ici à une autre idée.

La collecte des ordures n'est pas gratuite


Dans les API hautes performances telles que WebGL, WebAudio et WebGPU, qui est toujours en développement, les objets sont créés et supprimés dans des phases distinctes. Ces spécifications sont écrites afin que la récupération de place ne soit pas en cours. De plus, il n'y a même pas de promesse, mais pull () - vous demandez simplement à chaque image: "Quelque chose s'est-il passé ou non?".

Semispace aka Lisp 2


Il y a un autre collectionneur dont je veux parler. Et si vous ne libérez pas de mémoire, mais copiez tous les objets vivants quelque part dans un autre endroit.

Essayons de copier l'objet racine "tel quel", qui fait référence quelque part.



Et puis tout le monde.



Il n'y a aucun débris ou trou dans la mémoire ci-dessus. Tout semble aller bien, mais deux problèmes se posent:

  • Objets en double - nous avons deux objets verts et deux bleus. Lequel utiliser?
  • Les liens à partir de nouveaux objets mènent à d'anciens objets et non les uns aux autres.

Avec les liens, tout est résolu à l'aide d'une «magie» algorithmique spéciale, et nous pouvons faire face à la duplication d'objets en supprimant tout ci-dessous.


En conséquence, nous avons de l'espace libre et seulement des objets vivants dans l'ordre normal ci-dessus. Cet algorithme est appelé Semispace , Lisp 2, ou simplement le «collecteur de copie».

Avantages:

  • Mémoire de défragmentation.
  • C'est simple.
  • Peut être combiné avec une phase de dérivation.
  • Il fonctionne proportionnellement au nombre d'objets vivants dans le temps.
  • Fonctionne bien quand il y a beaucoup de déchets. Si vous avez 2 Go de mémoire et 3 objets, vous contournerez seulement 3 objets et les 2 Go restants semblent avoir disparu.

Inconvénients:

  • Double consommation de mémoire. Vous utilisez la mémoire 2 fois plus que nécessaire.
  • Déplacer des objets n'est pas non plus une opération très bon marché.

Remarque: les ramasse-miettes peuvent déplacer des objets.

Sur le Web, cela n'est pas pertinent, mais sur Node.js même beaucoup. Si vous écrivez l'extension en C ++, le langage ne sait pas tout cela, il y a donc des liens doubles appelés handle et ressemblent à ceci: v8 :: Local <v8 :: String>.

Par conséquent, si vous allez écrire des plugins pour Node.js, les informations vous seront utiles.

Nous résumons les différents algorithmes avec leurs avantages et inconvénients dans le tableau. Il a également un algorithme Eden, mais à ce sujet plus tard.



Je veux vraiment un algorithme sans contre, mais ce n'est pas le cas. Par conséquent, nous prenons le meilleur de tous les mondes: nous utilisons plusieurs algorithmes en même temps. Dans un morceau de mémoire, nous collectons les ordures avec un algorithme, et dans un autre avec un autre algorithme.

Comment comprendre l'efficacité de l'algorithme dans une telle situation?

Nous pouvons utiliser les connaissances de maris intelligents des années 60 qui ont examiné tous les programmes et réalisé:

Hypothèse générationnelle faible: la plupart des objets meurent jeunes.

Ils voulaient dire que tous les programmes ne font que produire des ordures. Pour tenter d'utiliser les connaissances, nous arriverons à ce que l'on appelle «l'assemblage par les générations».

Assemblage générationnel


Nous créons deux morceaux de mémoire qui ne sont en aucun cas connectés: à gauche, Eden, et à droite, Mark et Sweep lents. En Eden, nous créons des objets. Beaucoup d'objets.



Quand Eden dit qu'il est plein, nous commençons la collecte des ordures. Nous trouvons des objets vivants et les copions vers un autre collectionneur.



Eden lui-même est complètement nettoyé et nous pouvons y ajouter des objets.



En nous basant sur l'hypothèse des générations, nous avons décidé que les objets c, g, i vivraient très probablement longtemps, et vous pouvez les vérifier moins souvent. Connaissant cette hypothèse, vous pouvez écrire des programmes qui trompent le collectionneur. Cela peut être fait, mais je ne vous conseille pas, car cela entraînera presque toujours des effets indésirables. Si vous créez des déchets à vie longue, le collectionneur commencera à croire qu'ils n'ont pas besoin d'être collectés.

Un exemple classique de tricherie est LRU-cache. Un objet reste longtemps dans le cache, le collectionneur le regarde et pense qu'il ne le collectera pas encore, car l'objet va vivre très longtemps. Ensuite, un nouvel objet entre dans le cache, et un grand ancien est poussé hors de lui et il n'est plus possible d'assembler immédiatement ce grand objet.

Comment collecter maintenant nous savons. Parlez du moment de la collecte.

Quand collecter?


L'option la plus simple consiste à tout arrêter , à démarrer la génération, puis à recommencer le travail JS.



Dans les ordinateurs modernes, plus d'un thread d'exécution. Sur le Web, cela est connu des Web Workers. Pourquoi ne pas prendre et paralléliser le processus d'assemblage . Effectuer plusieurs petites opérations en même temps sera plus rapide qu'une grande.



Une autre idée est de faire soigneusement un instantané de l'état actuel et de le construire en parallèle avec JS .



Si cela vous intéresse, je vous conseille de lire:

  • Le seul et principal livre d'assemblage, le Garbage Collection Handbook.
  • Wikipédia comme ressource universelle.
  • Site Web memorymanagement.org.
  • Rapports et articles d' Alexander Shepelev . Il parle de Java, mais en termes d'ordures, Java et V8 fonctionnent à peu près de la même manière.

Réalité du navigateur


Passons à la façon dont les navigateurs utilisent tout ce dont nous avons parlé.

Moteurs IoT


Commençons pas par les navigateurs, mais par les moteurs de l'Internet des objets: JerryScript et Duktape. Ils utilisent les algorithmes Mark'n'sweep et Stop the world.

Les moteurs IoT fonctionnent sur les microcontrôleurs, ce qui signifie: le langage est lent; deuxième se bloque; fragmentation et tout ça pour une théière avec éclairage :)

Si vous écrivez Internet des objets en JavaScript, dites-le nous dans les commentaires? y a-t-il un point

Nous laisserons les moteurs IoT tranquilles, nous sommes intéressés par:

  • V8.
  • SpiderMonkey En fait, il n'a pas de logo. Logo fait maison :)
  • JavaScriptCore utilisé par WebKit.
  • ChakraCore utilisé dans Edge.



Tous les moteurs sont à peu près les mêmes, nous parlerons donc du V8, le plus célèbre.

V8


  • Presque tout le JavaScript côté serveur, car il s'agit de Node.js.
  • Près de 80% du JavaScript côté client.
  • Les développeurs les plus sociables, il y a beaucoup d'informations et de bons codes sources qui sont plus faciles à lire.

Le V8 utilise l'assemblage générationnel.


La seule différence est que nous avions auparavant deux collecteurs, et maintenant trois:

  • Un objet est créé dans Eden.
  • À un moment donné dans Eden, il y a trop de déchets et l'objet est transféré vers Semispace.
  • L'objet est jeune et lorsque le collectionneur se rend compte qu'il est trop vieux et ennuyeux, il le jette dans Mark and Sweep, dans lequel la collecte des ordures est extrêmement rare.

Vous pouvez clairement voir à quoi cela ressemble sur la trace de la mémoire .



Plusieurs grosses vagues avec de petites vagues sont perceptibles. Les petits sont des assemblages mineurs et les grands sont des assemblages majeurs.

Le sens de notre existence, selon l'hypothèse générationnelle, est de générer des ordures, donc l'erreur suivante est la peur de créer des ordures.

La corbeille peut être créée lorsqu'elle est vraiment une corbeille. , , , .

mark


V8 .


Stop the world, , JS, .

?


1 3%, .

3% = 1/33 GameDev. GameDev 3% 1 , . GameDev .

 const pool = [new Bullet(), new Bullet(), /* ... */]; function getFromPool() { const bullet = pool.find(x => !x.inUse); bullet.isUse = true; return bullet; } function returnToPool(bullet) { bullet.inUse = false; } // Frame const bullet = getFromPool(); // ... returnToPool(bullet); 

, , 10 000 .

— . , . , .

: Chromium


, , , Chromium.

 > performance.memory MemoryInfo { totalJSHeapSize: 10000000, usedJSHeapSize: 10000000, jsHeapSizeLimit: 2330000000 } 

Chromium performance.memory , , Chromium .

: Chromium 2 JavaScript.

, .

: Node


Node.js process.memoryUsage , .

 > process.memoryUsage() { rss: 22839296, heapTotal: 10207232, heapUsed: 5967968, external: 12829 } 

, - , . . .



— , . proposal , .

Node.js, c node-weak , , .

 let cached = new WeakRef(myJson); // 2   let json = cached.deref(); if (!json) { json = await fetchAgain(); } 

, , - JS. , , , .

WebAssembly , . , , , .

: v8.dev JS.


?



DevTools : Performance Memory . Chromium, , Firefox Safari .

Performance


Trace, «Memory» Performance, JS .



JS V8 , . . , GC 30 1200 JS, 1/40.

Memory


.



.



, . , , , V8 , . , .

, , Q ( compiled code) — React . , ?

, , , .

, .



, , , . , — 4 . , .



React, - : . , JSX.

Performance Memory , :

  • Chromium: about:tracing.
  • Firefox: about:memory about:performance, .
  • Node — trace-gc, —expose-gc, require('trace_events'). trace_events .

Résumé


  • , , , .
  • .
  • . , ?
  • , - .
  • SPA, , 1 , .
  • , - .

: flapenguin.me , Twitter , GitHub .

- ++ . YouTube-
.

, 2018 , . Frontend Conf 2018.

, :)

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


All Articles