J'ai une application Javascript assez complexe, qui a une boucle principale appelée 60 fois par seconde. Il semble y avoir beaucoup de ramasse-miettes en cours (basé sur la sortie `` en dents de scie '' de la chronologie de la mémoire dans les outils de développement Chrome) - et cela a souvent un impact sur les performances de l'application.
J'essaie donc de rechercher les meilleures pratiques pour réduire la quantité de travail que le garbage collector doit effectuer. (La plupart des informations que j'ai pu trouver sur le Web concernent éviter les fuites de mémoire, ce qui est une question légèrement différente - ma mémoire se libère, c'est juste qu'il y a trop de ramasse-miettes en cours.) Je suppose que cela revient principalement à réutiliser des objets autant que possible, mais bien sûr, le diable est dans les détails.
L'application est structurée en «classes» sur le modèle de l'héritage JavaScript simple de John Resig .
Je pense qu'un problème est que certaines fonctions peuvent être appelées des milliers de fois par seconde (car elles sont utilisées des centaines de fois à chaque itération de la boucle principale), et peut-être les variables de travail locales dans ces fonctions (chaînes, tableaux, etc.) pourrait être le problème.
Je suis conscient de la mise en commun d'objets pour des objets plus gros / plus lourds (et nous l'utilisons dans une certaine mesure), mais je recherche des techniques qui peuvent être appliquées à tous les niveaux, en particulier en ce qui concerne les fonctions qui sont appelées très souvent dans des boucles serrées. .
Quelles techniques puis-je utiliser pour réduire la quantité de travail que le garbage collector doit effectuer?
Et peut-être aussi - quelles techniques peuvent être employées pour identifier les objets qui sont le plus ramassés? (C'est une base de code très volumineuse, donc comparer des instantanés du tas n'a pas été très fructueux)
la source
Réponses:
Beaucoup de choses que vous devez faire pour minimiser le taux de désabonnement GC vont à l'encontre de ce qui est considéré comme idiomatique JS dans la plupart des autres scénarios, alors gardez à l'esprit le contexte lorsque vous jugez les conseils que je donne.
L'attribution se produit dans les interprètes modernes à plusieurs endroits:
new
ou via la syntaxe littérale[...]
, ou{}
.(function (...) { ... })
.Object(myNumber)
ouNumber.prototype.toString.call(42)
Array.prototype.slice
.arguments
pour réfléchir sur la liste des paramètres.Évitez de faire cela et regroupez et réutilisez les objets lorsque cela est possible.
Plus précisément, recherchez des opportunités pour:
split
ou les correspondances d'expressions régulières, car chacune nécessite plusieurs allocations d'objets. Cela se produit fréquemment avec les clés dans les tables de recherche et les ID de nœud DOM dynamiques. Par exemple,lookupTable['foo-' + x]
et lesdocument.getElementById('foo-' + x)
deux impliquent une allocation car il existe une concaténation de chaînes. Souvent, vous pouvez attacher des clés à des objets de longue durée au lieu de les reconcaténer. Selon les navigateurs que vous devez prendre en charge, vous pourrez peut-être utiliserMap
directement des objets comme clés.try { op(x) } catch (e) { ... }
, faitesif (!opCouldFailOn(x)) { op(x); } else { ... }
.JSON.stringify
qui utilise un tampon natif interne pour accumuler du contenu au lieu d'allouer plusieurs objets.arguments
depuis les fonctions qui utilisent qui doivent créer un objet de type tableau lorsqu'elles sont appelées.J'ai suggéré d'utiliser
JSON.stringify
pour créer des messages réseau sortants. L'analyse des messages d'entréeJSON.parse
implique évidemment une allocation, et beaucoup pour les messages volumineux. Si vous pouvez représenter vos messages entrants sous forme de tableaux de primitives, vous pouvez économiser de nombreuses allocations. Le seul autre élément intégré autour duquel vous pouvez créer un analyseur qui n'alloue pas estString.prototype.charCodeAt
. Un analyseur pour un format complexe qui n'utilise que ce qui va être infernal à lire.la source
JSON.parse
objets d allouent moins (ou autant) d'espace que la chaîne de message?Les outils de développement Chrome ont une fonctionnalité très intéressante pour tracer l'allocation de mémoire. Cela s'appelle la chronologie de la mémoire. Cet article décrit quelques détails. Je suppose que c'est ce dont vous parlez concernant la "dent de scie"? Il s'agit d'un comportement normal pour la plupart des environnements d'exécution GC. L'allocation se poursuit jusqu'à ce qu'un seuil d'utilisation soit atteint, déclenchant une collecte. Normalement, il existe différents types de collections à différents seuils.
Les récupérations de mémoire sont incluses dans la liste d'événements associée à la trace avec leur durée. Sur mon cahier assez ancien, des collections éphémères se produisent à environ 4 Mo et prennent 30 ms. Il s'agit de 2 de vos itérations de boucle à 60 Hz. S'il s'agit d'une animation, les collections de 30 ms provoquent probablement un bégaiement. Vous devriez commencer ici pour voir ce qui se passe dans votre environnement: où se situe le seuil de collecte et combien de temps vos collections prennent. Cela vous donne un point de référence pour évaluer les optimisations. Mais vous ne ferez probablement pas mieux que de diminuer la fréquence du bégaiement en ralentissant le taux d'allocation, allongeant ainsi l'intervalle entre les collectes.
La prochaine étape consiste à utiliser les profils | Fonction Record Heap Allocations pour générer un catalogue d'allocations par type d'enregistrement. Cela montrera rapidement quels types d'objets consomment le plus de mémoire pendant la période de trace, ce qui est équivalent au taux d'allocation. Concentrez-vous sur ces derniers par ordre décroissant de taux.
Les techniques ne sont pas sorcières. Évitez les objets en boîte lorsque vous pouvez le faire avec un sans boîte. Utilisez des variables globales pour contenir et réutiliser des objets en boîte uniques plutôt que d'allouer de nouveaux objets à chaque itération. Regroupez les types d'objets communs dans des listes gratuites plutôt que de les abandonner. Mettez en cache les résultats de concaténation de chaînes qui sont probablement réutilisables dans les itérations futures. Évitez l'allocation juste pour renvoyer des résultats de fonction en définissant des variables dans une portée englobante à la place. Vous devrez considérer chaque type d'objet dans son propre contexte pour trouver la meilleure stratégie. Si vous avez besoin d'aide pour des détails, publiez une modification décrivant les détails du défi que vous envisagez.
Je déconseille de pervertir votre style de codage normal tout au long d'une application dans une tentative de fusil de chasse pour produire moins de déchets. C'est pour la même raison que vous ne devez pas optimiser la vitesse prématurément. La plupart de vos efforts ainsi qu'une grande partie de la complexité et de l'obscurité supplémentaires du code n'auront aucun sens.
la source
request animation frame
,animation frame fired
etcomposite layers
. Je ne sais pas pourquoi je ne vois pasGC Event
comme vous (c'est sur la dernière version de chrome, et aussi canary).@342342
etcode relocation info
.En règle générale, vous voudriez mettre en cache autant que possible et faire aussi peu de création et de destruction pour chaque exécution de votre boucle.
La première chose qui me vient à l'esprit est de réduire l'utilisation de fonctions anonymes (si vous en avez) dans votre boucle principale. De plus, il serait facile de tomber dans le piège de la création et de la destruction d'objets qui sont passés à d'autres fonctions. Je ne suis en aucun cas un expert javascript, mais j'imagine que ceci:
fonctionnerait beaucoup plus vite que cela:
Y a-t-il des temps d'arrêt pour votre programme? Peut-être que vous en avez besoin pour fonctionner correctement pendant une seconde ou deux (par exemple pour une animation) et ensuite il a plus de temps à traiter? Si tel est le cas, je pourrais voir prendre des objets qui seraient normalement récupérés tout au long de l'animation et garder une référence à eux dans un objet global. Ensuite, lorsque l'animation se termine, vous pouvez effacer toutes les références et laisser le garbage collector faire son travail.
Désolé si tout cela est un peu trivial par rapport à ce que vous avez déjà essayé et pensé.
la source
Je créerais un ou quelques objets dans le
global scope
(où je suis sûr que le garbage collector n'est pas autorisé à les toucher), puis j'essaierais de refactoriser ma solution pour utiliser ces objets pour faire le travail, au lieu d'utiliser des variables locales .Bien sûr, cela ne peut pas être fait partout dans le code, mais en général, c'est ma façon d'éviter le garbage collector.
PS Cela pourrait rendre cette partie spécifique du code un peu moins maintenable.
la source