Avez-vous déjà utilisé une application Flash et remarqué un décalage? Vous ne savez toujours pas pourquoi ce jeu flash cool tourne lentement sur votre ordinateur? Si vous voulez en savoir plus sur une cause possible, alors cet article est pour vous.
Nous avons trouvé cet auteur génial grâce à FlashGameLicense.com, le lieu d'achat et de vente de jeux Flash.!
Tutoriel republiéToutes les quelques semaines, nous revoyons certains des articles préférés de nos lecteurs tout au long de l'histoire du site. Ce tutoriel a été publié pour la première fois en juin 2010.
Jetons un coup d'œil au résultat final sur lequel nous allons travailler:
Avant d'entrer dans le vif du sujet, vous devez d'abord connaître le fonctionnement de l'instanciation et du référencement dans AS3. Si vous avez déjà lu à ce sujet, je recommande toujours de lire cette petite étape. De cette façon, toutes les connaissances seront fraîches dans votre tête et vous n'aurez pas de difficulté à lire le reste de ce petit conseil.!
La création et la référence d'instances dans AS3 sont différentes de ce que la plupart des gens pensent. L'instanciation (ou "création") de quelque chose ne se produit que lorsque le code demande de créer un objet. Cela se produit généralement avec le "nouveau" mot clé, mais il est également présent lorsque vous utilisez un syntaxe littérale ou définir des paramètres pour des fonctions, par exemple. Des exemples de ceci sont montrés ci-dessous:
// Instanciation via le "nouveau" mot clé new Object (); new Array (); new int (); new String (); new Boolean (); nouvelle date (); // Instanciation par la syntaxe littérale ; []; 5 "Bonjour tout le monde!" true // Instanciation via les paramètres de fonction function privée tutExample (paramètre1: int, paramètre2: Booléen): void
Lorsqu'un objet est créé, il reste seul jusqu'à ce que quelque chose le référence. Pour ce faire, vous créez généralement une variable et lui transmettez la valeur de l'objet afin qu'il sache quel objet il contient actuellement. Cependant (et c'est la partie que la plupart des gens ne connaissent pas), lorsque vous transmettez la valeur d'une variable à une autre variable, vous ne créez pas un nouvel objet. Vous créez plutôt un autre lien vers l'objet que les deux variables contiennent maintenant! Voir l'image ci-dessous pour plus de précisions:
L'image assume les deux Variable 1 et Variable 2 peut tenir le smiley (c’est-à-dire qu’ils peuvent avoir le même type). Dans le côté gauche, seulement Variable 1 existe. Cependant, lorsque nous créons et définissons Variable 2 à la même valeur de Variable 1, nous ne créons pas un lien entre Variable 1 et Variable 2 (partie supérieure droite de l'image), nous créons plutôt un lien entre le smiley et Variable 2 (partie inférieure droite de l'image).
Avec cette connaissance, nous pouvons passer au ramasse-miettes.
Il est évident que chaque application nécessite une certaine quantité de mémoire pour s'exécuter, de même que des variables pour contenir des valeurs et les utiliser. Ce qui n'est pas clair, c'est comment l'application gère les objets qui ne sont plus nécessaires. Est-ce que ça les recycle? Est-ce qu'il les supprime? Laisse-t-il l'objet en mémoire jusqu'à la fermeture de l'application? Les trois options peuvent arriver, mais nous allons parler ici des deuxième et troisième.
Imaginons une situation dans laquelle une application crée beaucoup d'objets lors de son initialisation, mais une fois cette période terminée, plus de la moitié des objets créés restent inutilisés. Que se passerait-il s'ils restaient dans la mémoire? Ils prendraient certainement beaucoup de place dedans, causant ainsi ce que les gens appellent décalage, ce qui est un ralentissement notable dans l'application. La plupart des utilisateurs n'aimeraient pas cela, nous devons donc l'éviter. Comment pouvons-nous coder afin de rendre l'application plus efficace? La réponse est dans le Éboueur.
Le ramasse-miettes est une forme de gestion de la mémoire. Il vise à éliminer tout objet non utilisé occupant de l’espace dans la mémoire du système. De cette façon, l'application peut fonctionner avec une utilisation de mémoire minimale. Voyons voir comment ça fonctionne:
Lorsque votre application commence à s'exécuter, elle demande au système de disposer d'une quantité de mémoire qui sera utilisée par l'application. L'application commence ensuite à remplir cette mémoire avec les informations dont vous avez besoin. chaque objet que vous créez y va. Toutefois, si l'utilisation de la mémoire se rapproche de la mémoire demandée initialement, le récupérateur de place s'exécute en recherchant tout objet non utilisé pour vider un espace dans la mémoire. Parfois, cela provoque un peu de retard dans l'application, en raison de la lourde charge de la recherche d'objet.
Dans l'image, vous pouvez voir le pics de mémoire (entouré en vert). Les pics et la chute soudaine sont causés par le ramasse-miettes, qui agit lorsque l'application a atteint l'utilisation de mémoire demandée (la ligne rouge), supprimant tous les objets inutiles..
Maintenant que nous savons ce que le ramasse-miettes peut faire pour nous, il est temps d'apprendre à coder pour en tirer tous les avantages. Tout d’abord, nous devons savoir comment fonctionne le ramasse-miettes, d’un point de vue pratique. Dans le code, les objets deviennent éligibles pour la récupération de place lorsqu'ils deviennent inaccessibles. Quand un objet est inaccessible, le code comprend qu'il ne sera plus utilisé, il doit donc être collecté.
Actionscript 3 vérifie l'accessibilité via racines de collecte des ordures. Au moment où un objet ne peut pas être accédé via une racine de la récupération de place, il devient éligible pour la collecte. Vous trouverez ci-dessous une liste des principales racines de la récupération de place:
Afin de comprendre comment les objets sont gérés par le récupérateur de place, nous devons coder et examiner ce qui se passe dans le fichier d'exemple. J'utiliserai le projet AS3 de FlashDevelop et le compilateur de Flex, mais je suppose que vous pouvez le faire sur n'importe quel IDE de votre choix, car nous n'utiliserons pas d'éléments spécifiques qui n'existent que dans FlashDevelop. J'ai construit un fichier simple avec un bouton et une structure de texte. Comme ce n'est pas l'objectif de cette astuce, je vais l'expliquer rapidement: lorsqu'un bouton est cliqué, une fonction est activée. À tout moment, nous souhaitons afficher du texte à l'écran, vous appelez une fonction avec le texte et celui-ci s'affiche. Il y a aussi un autre champ de texte pour montrer une description des boutons.
L'objectif de notre exemple de fichier est de créer des objets, de les supprimer et d'examiner leur sort après leur suppression. Nous aurons besoin d'un moyen de savoir si l'objet est actif ou non. Nous allons donc ajouter un écouteur ENTER_FRAME à chacun des objets et leur faire afficher du texte avec la durée de leur vie. Alors codons le premier objet!
J'ai créé une image de smiley amusante pour les objets, en hommage au formidable tutoriel de jeu Avoider de Michael James Williams, qui utilise également des images de smileys. Chaque objet aura un numéro sur sa tête, afin que nous puissions l'identifier. Aussi, j'ai nommé le premier objet L'objet1, et le deuxième objet L'objet2, il sera donc facile de distinguer. Allons au code:
private var _theObject1: TheObject1; fonction privée newObjectSimple1 (e: MouseEvent): void // S'il existe déjà un objet créé, ne faites rien si (_theObject1) return; // Créez le nouvel objet, définissez-le à la position dans laquelle il devrait se trouver et ajoutez-le à la liste d'affichage afin que nous puissions voir qu'il a été créé _theObject1 = new TheObject1 (); _theObject1.x = 320; _theObject1.y = 280; _theObject1.addEventListener (Event.ENTER_FRAME, changeTextField1); addChild (_theObject1);
Le deuxième objet a presque la même apparence. C'est ici:
private var _theObject2: TheObject2; fonction privée newObjectSimple2 (e: MouseEvent): void // S'il existe déjà un objet créé, ne faites rien si (_theObject2) return; // Créez le nouvel objet, définissez-le à la position dans laquelle il devrait se trouver et ajoutez-le à la liste d'affichage afin que nous puissions voir qu'il a été créé _theObject2 = new TheObject2 (); _theObject2.x = 400; _theObject2.y = 280; _theObject2.addEventListener (Event.ENTER_FRAME, changeTextField2); addChild (_theObject2);
Dans le code, newObjectSimple1 () et newObjectSimple2 () sont des fonctions qui sont déclenchées lorsque l'utilisateur clique sur le bouton correspondant. Ces fonctions créent simplement un objet et l'ajoutent à l'écran pour que nous sachions qu'il a été créé. En outre, il crée un ENTER_FRAME écouteur d'événements dans chaque objet, ce qui leur fera afficher un message toutes les secondes, tant qu'ils sont actifs. Voici les fonctions:
fonction privée changeTextField1 (e: Event): void // Notre exemple tourne à 30 images par seconde, ajoutons donc 1/30 sur chaque image du compte. _objectCount1 + = 0.034; // Vérifie si _objectCount1 a passé une seconde supplémentaire if (int (_objectCount1)> _secondCount1) // Affiche un texte dans l'écran displayText ("L'objet 1 est vivant…" + int (_objectCount1)); _secondCount1 = int (_objectCount1);
fonction privée changeTextField2 (e: Event): void // Notre exemple tourne à 30 images par seconde, ajoutons donc 1/30 sur chaque image du compte. _objectCount2 + = 0.034; // Vérifie si _objectCount2 a passé une seconde supplémentaire if (int (_objectCount2)> _secondCount2) // Affiche un texte dans l'écran displayText ("Object 2 est vivant…" + int (_objectCount2)); _secondCount2 = int (_objectCount2);
Ces fonctions affichent simplement un message à l'écran indiquant l'heure à laquelle les objets ont été en vie. Voici le fichier SWF avec l'exemple actuel:
Maintenant que nous avons couvert la création d'objets, essayons quelque chose: vous êtes-vous déjà demandé ce qui se passerait si vous supprimiez (supprimez toutes les références) un objet? Est-ce que les ordures sont ramassées? C'est ce que nous allons tester maintenant. Nous allons créer deux boutons de suppression, un pour chaque objet. Faisons le code pour eux:
fonction privée deleteObject1 (e: MouseEvent): void // Vérifie si _theObject1 existe réellement avant de le supprimer de la liste d'affichage si (_theObject1 && contient (_theObject1)) removeChild (_theObject1); // Suppression de toutes les références à l'objet (c'est la seule référence) _theObject1 = null; // Affiche un texte à l'écran displayText ("Objet supprimé 1 avec succès!");
fonction privée deleteObject2 (e: MouseEvent): void // Vérifiez si _theObject2 existe vraiment avant de le supprimer de la liste d'affichage si (_theObject1 && contient (_theObject2)) removeChild (_theObject2); // Suppression de toutes les références à l'objet (c'est la seule référence) _theObject2 = null; // Affiche un texte à l'écran displayText ("Objet supprimé 2 avec succès!");
Jetons un coup d'oeil au SWF maintenant. Que penses-tu qu'il va se passer?
Comme vous pouvez le voir. Si vous cliquez sur "Create Object1" puis sur "Delete Object1", rien ne se passe vraiment! Nous pouvons dire que le code est exécuté, car le texte apparaît à l'écran, mais pourquoi l'objet n'est-il pas supprimé? L'objet est toujours là car il n'a pas été supprimé. Lorsque nous avons supprimé toutes les références, nous avons demandé au code de le rendre éligible pour le nettoyage de la mémoire, mais le ramasse-miettes ne fonctionne jamais. N'oubliez pas que le ramasse-miettes ne s'exécutera que lorsque l'utilisation actuelle de la mémoire sera proche de la mémoire demandée au démarrage de l'application. Cela a du sens, mais comment allons-nous tester cela?
Je ne vais certainement pas écrire un morceau de code pour remplir notre application d'objets inutiles jusqu'à ce que la mémoire utilisée devienne trop grosse. Selon l'article de Grant Skinner, nous utiliserons plutôt une fonction actuellement non prise en charge par Adobe, qui oblige Garbage Collector à s'exécuter. De cette façon, nous pouvons déclencher cette méthode simple et voir ce qui se passe quand elle s'exécute. De plus, à partir de maintenant, je vais appeler Garbage Collector le nom de GC, par souci de simplicité. Voici la fonction:
fonction privée forceGC (e: MouseEvent): void try new LocalConnection (). connect ('foo'); new LocalConnection (). connect ('foo'); catch (e: *) // Affiche un texte à l'écran displayText ("----- Récupération de place déclenchée -----");
Cette fonction simple, qui ne crée que deux objets LocalConnection (), est connue pour forcer le CPG à s'exécuter. Nous l'appellerons donc lorsque nous voulons que cela se produise. Je ne recommande pas d'utiliser cette fonction dans une application sérieuse. Si vous le testez, il n’ya pas de réel problème, mais s’il s’agit d’une application distribuée aux utilisateurs, ce n’est pas une bonne fonction, car elle peut avoir des effets négatifs..
Ce que je recommande dans des cas comme celui-ci, c’est que vous laissiez le GC fonctionner à son propre rythme. N'essayez pas de le forcer. Concentrez-vous plutôt sur le codage afin d’éviter les problèmes de mémoire (nous en parlerons à l’étape 6). Examinons à nouveau notre exemple de fichier SWF, puis cliquez sur le bouton "Collecter les ordures" après avoir créé et supprimé un objet..
Avez-vous testé le fichier? Ça a marché! Vous pouvez voir que maintenant, après la suppression d'un objet et le déclenchement du GC, l'objet est supprimé! Notez que si vous ne supprimez pas l'objet et appelez le CPG, rien ne se passera, car il y a toujours une référence à cet objet dans le code. Maintenant, si on essayait de garder deux références à un objet et d'en supprimer une?
Maintenant que nous avons prouvé que le CG fonctionne exactement comme nous le souhaitions, essayons autre chose: liez une autre référence à un objet (Object1) et supprimez l'original. Premièrement, nous devons créer une fonction pour lier et dissocier une référence à notre objet. Faisons le:
fonction privée saveObject1 (e: MouseEvent): void // _onSave est un booléen pour vérifier si nous devons lier ou dissocier la référence if (_onSave) // S'il n'y a pas d'objet à enregistrer, ne rien faire si (! _theObject1) // Affiche un texte à l'écran displayText ("Il n'y a pas d'objet 1 à enregistrer!"); revenir; // Une nouvelle variable pour contenir une autre référence à Object1 _theSavedObject = _theObject1; // Affiche un texte à l'écran displayText ("Objet enregistré 1 avec succès!"); // À la prochaine exécution de cette fonction, dissociez-la, car nous venons de lier _onSave = false; else // Suppression de la référence à celle-ci _theSavedObject = null; // Affiche un texte à l'écran displayText ("Objet non enregistré 1 avec succès!"); // Lors de la prochaine exécution de cette fonction, liez-la, car nous venons de dissocier _onSave = true;
Si nous testons notre fichier swf maintenant, nous remarquerons que si nous créons Object1, enregistrez-le, supprimez-le et forcez le GC à s'exécuter, rien ne se passera. En effet, même si nous supprimions le lien "original" vers l'objet, il y a encore une autre référence à celui-ci, ce qui l'empêche d'être éligible pour la récupération de place. C’est en gros tout ce que vous devez savoir sur le ramasse-miettes. Ce n'est pas un mystère, après tout. mais comment appliquer cela à notre environnement actuel? Comment pouvons-nous utiliser ces connaissances pour empêcher notre application de s'exécuter lentement? C’est ce que nous montrera l’étape 6: comment l’appliquer dans des exemples réels.
Passons maintenant à la meilleure partie: faire en sorte que votre code fonctionne efficacement avec le GC! Cette étape fournira des informations utiles que vous devriez conserver toute votre vie - sauvegardez-les correctement! Tout d'abord, j'aimerais présenter une nouvelle façon de construire vos objets dans votre application. C'est un moyen simple mais efficace de collaborer avec le GC. De cette façon, introduit deux classes simples, qui peuvent être étendues à d’autres, une fois que vous comprenez ce qu’il fait.
L'idée de cette façon est d'implémenter une fonction - appelée destroy () - sur chaque objet que vous créez et de l'appeler chaque fois que vous avez fini de travailler avec un objet. La fonction contient tout le code nécessaire pour supprimer toutes les références à et de l'objet (à l'exception de la référence utilisée pour appeler la fonction), afin de vous assurer que l'objet laisse votre application totalement isolée et qu'il est facilement reconnu par le GC. La raison en est expliquée à l'étape suivante. Regardons le code général de la fonction:
// Crée ceci dans chaque objet utilisé avec la fonction publique destroy (): void // Supprime les écouteurs d'événement // Supprime tout élément de la liste d'affichage // Efface les références aux autres objets afin qu'il soit totalement isolé //… // Pour supprimer l'objet, procédez comme suit: theObject.destroy (); // Et puis null la dernière référence à cela theObject = null;
Dans cette fonction, vous devrez tout effacer de l'objet pour qu'il reste isolé dans l'application. Après cela, il sera plus facile pour le GC de localiser et de supprimer l’objet. Examinons maintenant certaines des situations dans lesquelles la plupart des erreurs de mémoire se produisent:
Bien que travailler avec le GC soit génial, ce n’est pas parfait. Vous devez faire attention à ce que vous faites, sinon de mauvaises choses peuvent arriver avec votre application. J'aimerais montrer un problème qui peut survenir si vous ne suivez pas toutes les étapes requises pour que votre code fonctionne correctement avec le GC..
Parfois, si vous n'effacez pas toutes les références vers et à partir d'un objet, vous pouvez rencontrer ce problème, notamment si vous liez plusieurs objets entre eux dans votre application. Parfois, une seule référence laissée peut être suffisante pour que cela se produise: tous vos objets forment un îlot de références, dans lequel tous les objets sont connectés aux autres, ce qui empêche le GC de les supprimer..
Lors de son exécution, le CPG effectue deux tâches simples pour vérifier les objets à supprimer. L'une de ces tâches consiste à compter le nombre de références de chaque objet. Tous les objets avec 0 références sont collectés en même temps. L'autre tâche consiste à vérifier s'il existe un petit groupe d'objets liés les uns aux autres, mais inaccessibles, ce qui gaspille de la mémoire. Vérifiez l'image:
Comme vous pouvez le constater, les objets verts ne peuvent pas être atteints, mais leur comptage de référence est égal à 1. Le CPG effectue la deuxième tâche pour vérifier ce bloc d'objets et les supprimer tous. Cependant, lorsque le bloc est trop gros, le CPG "abandonne" la vérification et suppose que les objets peuvent être atteints. Maintenant, imaginez si vous avez quelque chose comme ça:
C'est l'île des références. Cela prendrait beaucoup de mémoire du système et ne serait pas collecté par le GC en raison de sa complexité. Ca sonne pas mal, hein? Il peut être facilement évité, cependant. Assurez-vous simplement que vous avez effacé toutes les références à un objet, puis des choses effrayantes comme celles-là ne se produiront plus.!
C'est ça pour l'instant. Dans ce petit conseil, nous avons appris que nous pouvions améliorer et rendre notre code plus efficace afin de réduire les problèmes de décalage et de mémoire, le rendant ainsi plus stable. Pour ce faire, nous devons comprendre le fonctionnement des objets de référence dans AS3 et comment en tirer parti pour que le GC fonctionne correctement dans notre application. Bien que nous puissions améliorer notre application, nous devons faire attention à le faire - sinon, cela peut devenir encore plus salissant et plus lent.!
J'espère que vous avez aimé cette astuce simple. Si vous avez des questions, laissez un commentaire ci-dessous!