Développement d'un projet d'animation basé sur JavaScript.
J'ai remarqué que l'utilisation appropriée de setInterval()
, setTimeout()
et même requestAnimationFrame
alloue de la mémoire sans ma demande et provoque des appels fréquents de récupération de place. Plus d'appels GC = scintille :
Par exemple; lorsque j'exécute ce qui suit --- (code simple en appelant init () dans Google Chrome, l'allocation de mémoire + la récupération de place est correcte pendant les 20-30 premières secondes ...
function init()
{
var ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
return true
}
D'une manière ou d'une autre, dans une minute environ, commence une étrange augmentation de la mémoire allouée! Puisque init () n'est appelé qu'une seule fois, quelle est la raison de l'augmentation de la taille de la mémoire allouée?
(Modifier: chrome capture d'écran téléchargée)
REMARQUE # 1: Oui, j'ai essayé d'appeler clearInterval () avant le prochain setInterval (). Le problème reste le même!
REMARQUE # 2: Afin d'isoler le problème, je garde le code ci-dessus simple et stupide.
EDIT: réponse de Yury est mieux.
tl; dr IMO il n'y a pas de fuite de mémoire. La pente positive est simplement l'effet de setInterval et setTimeout. Les ordures sont collectées, comme le montrent les motifs en dents de scie, ce qui signifie par définition qu'il n'y a pas de fuite de mémoire. (Je pense).
Je ne suis pas sûr qu'il existe un moyen de contourner cette soi-disant "fuite de mémoire". Dans ce cas, "fuite de mémoire" fait référence à chaque appel à la fonction setInterval augmentant l'utilisation de la mémoire, comme le montrent les pentes positives dans le profileur de mémoire.
La réalité est qu'il n'y a pas de fuite de mémoire réelle: le garbage collector est toujours en mesure de collecter la mémoire. La fuite de mémoire par définition "se produit lorsqu'un programme informatique acquiert de la mémoire mais ne parvient pas à la restituer au système d'exploitation".
Comme le montrent les profils de mémoire ci-dessous, aucune fuite de mémoire ne se produit. L'utilisation de la mémoire augmente à chaque appel de fonction. L'OP s'attend à ce que, car il s'agit de la même fonction appelée à plusieurs reprises, il ne devrait pas y avoir d'augmentation de mémoire. Cependant, ce n'est pas le cas. La mémoire est consommée à chaque appel de fonction. Finalement, les déchets sont collectés, créant le motif en dents de scie.
J'ai exploré plusieurs façons de réorganiser les intervalles, et ils conduisent tous au même motif en dents de scie (bien que certaines tentatives conduisent à un ramassage des ordures qui ne se produit jamais car les références ont été conservées).
function doIt() {
console.log("hai")
}
function a() {
doIt();
setTimeout(b, 50);
}
function b() {
doIt();
setTimeout(a, 50);
}
a();
http://fiddle.jshell.net/QNRSK/14/
function b() {
var a = setInterval(function() {
console.log("Hello");
clearInterval(a);
b();
}, 50);
}
b();
http://fiddle.jshell.net/QNRSK/17/
function init()
{
var ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
console.log('Hello');
}
init();
http://fiddle.jshell.net/QNRSK/20/
function init()
{
window.ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
console.log('Hello');
clearInterval(window.ref);
init();
}
init();
http://fiddle.jshell.net/QNRSK/21/
Apparemment, setTimeout
et setInterval
ne font pas officiellement partie de Javascript (donc ils ne font pas partie de la v8). L'implémentation est laissée à l'implémenteur. Je vous suggère de jeter un œil à l'implémentation de setInterval et autres dans node.js
Le problème ici n'est pas dans le code lui-même, il ne fuit pas. C'est à cause de la façon dont le panneau Timeline est implémenté. Lorsque Timeline enregistre des événements, nous collectons des traces de pile JavaScript à chaque appel de rappel setInterval. La trace de pile est d'abord allouée dans le tas JS, puis copiée dans des structures de données natives. Une fois la trace de pile copiée dans l'événement natif, elle devient une ordure dans le tas JS. Cela se reflète sur le graphique. La désactivation de l'appel suivant http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/TimelineRecordFactory.cpp#L55 rend le graphique de la mémoire plat.
Il existe un bogue lié à ce problème: https://code.google.com/p/chromium/issues/detail?id=120186
Chaque fois que vous effectuez un appel de fonction, il crée un frame de pile . Contrairement à beaucoup d'autres langages, Javascript stocke le cadre de pile sur le tas, comme tout le reste. Cela signifie que chaque fois que vous appelez une fonction, ce que vous faites toutes les 50 ms, un nouveau cadre de pile est ajouté au tas. Cela s'additionne et est finalement récupéré.
C'est un peu inévitable, étant donné le fonctionnement de Javascript. La seule chose qui peut vraiment être faite pour l'atténuer est de rendre les cadres de pile aussi petits que possible, ce que je suis sûr que toutes les implémentations font.
Je voulais répondre à votre commentaire sur setInterval et le scintillement:
J'ai remarqué que l'utilisation correcte de setInterval (), setTimeout () et même requestAnimationFrame alloue de la mémoire sans ma demande et provoque des appels fréquents de récupération de place. Plus d'appels GC = scintille :
Vous voudrez peut-être essayer de remplacer l'appel setInterval par une fonction auto-invoquante less evil basée sur setTimeout. Paul Irish le mentionne dans le discours intitulé 10 choses que j'ai apprises de la source jQuery (vidéo --- (ici , notes ici voir # 2). Ce que vous faites est de remplacer votre appel à setInterval par une fonction qui s'invoque indirectement via setTimeout après avoir terminé le travail qu'il est censé faire . Pour citer le discours:
Beaucoup ont soutenu que setInterval est une mauvaise fonction. Il continue d'appeler une fonction à des intervalles spécifiés, que la fonction soit terminée ou non.
En utilisant votre exemple de code ci-dessus, vous pouvez mettre à jour votre fonction init à partir de:
function init()
{
var ref = window.setInterval(function() { draw(); }, 50);
}
à:
function init()
{
//init stuff
//awesome code
//start rendering
drawLoop();
}
function drawLoop()
{
//do work
draw();
//queue more work
setTimeout(drawLoop, 50);
}
Cela devrait aider un peu car:
J'espère que cela t'aides!
Essayez de le faire sans la fonction anonyme. Par exemple:
function draw()
{
return true;
}
function init()
{
var ref = window.setInterval(draw, 50);
}
Se comporte-t-il toujours de la même manière?
Chrome ne voit pratiquement pas de pression de mémoire de votre programme (1,23 Mo est une utilisation de mémoire très faible par rapport aux normes d'aujourd'hui), donc il ne pense probablement pas qu'il ait besoin de GC de manière agressive. Si vous modifiez votre programme pour utiliser plus de mémoire, vous verrez le garbage collector se déclencher. essaye ça:
<!html>
<html>
<head>
<title>Where goes memory?</title>
</head>
<body>
Greetings!
<script>
function init()
{
var ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
var ar = new Array();
for (var i = 0; i < 1e6; ++i) {
ar.Push(Math.Rand());
}
return true
}
init();
</script>
</body>
</html>
Lorsque je lance cela, j'obtiens un modèle d'utilisation de la mémoire des dents de scie, atteignant un pic autour de 13,5 Mo (encore une fois, assez petit par rapport aux normes d'aujourd'hui).
PS: Spécificités de mes navigateurs:
Google Chrome 23.0.1271.101 (Official Build 172594)
OS Mac OS X
WebKit 537.11 (@136278)
JavaScript V8 3.13.7.5
Flash 11.5.31.5
User Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11
Il ne semble pas y avoir de fuite de mémoire. Tant que l'utilisation de la mémoire diminue à nouveau après le GC et que l'utilisation globale de la mémoire n'a pas tendance à augmenter en moyenne, il n'y a pas de fuite.
La "vraie" question que je vois ici est que setInterval
utilise effectivement de la mémoire pour fonctionner, et il ne semble pas qu'il devrait allouer quoi que ce soit. En réalité, il doit allouer quelques éléments:
true
valeur de retour de draw()
.Chaque fois que votre fonction anonyme s'exécute, il y aura de la mémoire allouée. Lorsque ces allocations atteindront un certain seuil, le GC démarrera et nettoiera pour vous ramener au niveau de base. Le cycle continuera ainsi jusqu'à ce que vous l'éteigniez. Il s'agit d'un comportement attendu.
J'ai aussi le même problème. Le client m'a signalé que la mémoire de son ordinateur augmentait de plus en plus. Au début, je pensais qu'il était vraiment étrange qu'une application Web puisse le faire même si elle était accessible par un simple navigateur. J'ai remarqué que cela ne se produisait que dans Chrome.
Cependant, j'ai commencé avec un partenaire pour enquêter et à travers les outils de développement de Chrome et la tâche du gestionnaire, nous avons pu voir l'augmentation de la mémoire que le client m'avait signalée.
Ensuite, nous voyons qu'une fonction jquery (cadre d'animation de demande) a été chargée à plusieurs reprises pour augmenter la mémoire système. Après cela, nous avons vu grâce à ce post, un compte à rebours jquery faisait cela, car il a à l'intérieur un "SETINTERVAL" qui à chaque fois mettait à jour la date dans la mise en page de mon application.
Comme je travaille avec ASP.NET MVC, je viens de quitter ce compte à rebours de script jquery depuis BundleConfig et depuis ma mise en page, en remplaçant mon compte à rebours par le code suivant:
@(DateTime.Now.ToString("dd/MM/yyyy HH:mm"))