Object Pooling
23rd juillet, 2009 par FlozÇa fait un moment que je veux mettre à plat ce que j’ai compris vis à vis des techniques d’Object Pooling et de Linked List, et nous y voilà. On va commencer par l’Object Pooling.
L’Object Pooling est une structure de programmation (ou Design Pattern) qui consiste en une liste d’objets disponibles à l’utilisation.
En gros, au lieu de passer par un new PooledObject( params… ), on utilisera une méthode similaire à Pool.create( params… ), qui nous renverra le PooledObject en question (comme nous l’aurait fait new PooledObject( params… )). La différence principale réside dans le fait que, derrière, la classe Pool regorge un certains nombres d’objets qui n’attendent qu’à être utilisés.
Instancier un objet, et passer par l’opérateur new s’avère assez gourmand en ressources. Du coup, créer une liste d’objet s’avère intéressant car :
- On instancie les objets par pack (la quantité est à définir en fonction des besoins),
- Une seule référence pour toute la liste (et donc tous les objets) est nécessaires.
Cette structure nous offre donc un gain en terme de performances, et de mémoire.
Voyons ce que ça donne au niveau du code. J’ai fais quelques schémas aussi pour représenter visuellement ce qu’il se passe ![]()
A noter que le modèle de code provient du wiki de Joa Ebert. C’est à partir de ce lien que j’ai appris quasiment tout ce que je sais à propos de l’Object Pooling.
1) L’initialisation de la liste.
Dans le lien ci dessus, l’initialisation de la liste se fait au premier appel de la méthode statique create();
// - CONSTS ----------------------------------------------------------------------
private const GROWTH_RATE:int = 4;
// - PRIVATE VARIABLES -----------------------------------------------------------
private var _allowInstanciation:Boolean;
private var _availableInPool:int;
private var _currentPooledCircle:PooledCircle;
private var _next:PooledCircle;
public static function create():PooledCircle
{
var pooledCircle:PooledCircle;
if ( !_availableInPool )
{
var i:int = GROWTH_RATE;
while ( --i > -1 )
{
_allowInstanciation = true; {
pooledCircle = new PooledCircle();
} _allowInstanciation = false;
pooledCircle._next = _currentPooledCircle;
_currentPooledCircle = pooledCircle;
}
_availableInPool = GROWTH_RATE;
}
pooledCircle = _currentPooledCircle;
_currentPooledCircle = pooledCircle._next;
--_availableInPool;
return pooledCircle;
}
Ici, l’instanciation des objets se fait à partir d’une boucle while. Dans cet exemple, le premier objet crée au sein de cette boucle, sera le dernier disponible de la liste.
On note aussi l’existence d’une variable _currentPooledCircle. Celle ci contient la référence au prochain objet qui sera utilisé, et donc à l’objet renvoyé lors de l’appel de la méthode create().
A l’appel de la méthode create(), la valeur de _currentPooledCircle change : elle devient _currentPooledCircle._next, soit le prochain objet disponible.
Si _availableInPool arrive à 0, c’est qu’il n’y a plus d’objets disponibles. Ainsi, nous devons “refaire le plein” d’objets. A noter que lorsque _availableInPool est égal à 0, la valeur de _currentPoolCircle est null.
Bon, visuellement ça donne ça :

Initialisation de la liste d'objets
Les pastilles vertes correspondent à des objets disponibles.
La pastille vertes clair/tournant au jaune correspond à _currentPooledCircle et donc au prochain objet qui sera utilisé.
La pastille avec une croix orange correspond à un élément null.
Les flèches correspondent au lien entre chaque objet. Elles représentent la valeur de _next.
2) Création (récupération) d’un élément : appel à la méthode create().
Dans notre cas, l’initialisation se fait lors du premier appel de la méthode create(). Ainsi, l’initialisation sera directement suivi de l’attribution du premier objet disponible. Il y aura donc un objet disponible en moins dans la liste.
Le schéma qui va suivre représente plusieurs appel de la méthode create(), jusqu’à ce que _availableInPool soit égal à 0.

Création des éléments jusqu'à saturation.
Une nouvelle pastille fait son apparition : pastille rouge, pour illustrer un élément “utilisé”.
A la fin du schéma, nous sommes donc à :
- _availableInPool = 0;
- _currentPooledCircle = null;
Sur le schéma, _currentPooledCircle est représenté par la pastille avec la croix orange.
Le prochain appel à la méthode create() se résultera par un nouveau remplissage de la liste.
3) Ré-initialisation de la liste.
Voici le schéma représentatif :

Nouvel appel à la méthode create() à la fin du dernier schéma.
Ainsi, on conserve bien nos anciens objets. Ils sont toujours “là”.
Mais de nouveaux font leurs apparition, et eux ils sont disponibles à l’utilisation. A cet instant, la valeur de _availableInPool vient de passer à 4 (qui est la valeur de GROWTH_RATE).
Les deux dernières étapes se répèteront infiniment, au besoin.
4) Suppression d’un élément de la liste.
L’Object Pooling offrant comme principal avantage l’économisation de l’instanciation d’un objet (on privilégie la réutilisation), il serait intéressant de pouvoir rendre de nouveau disponible un objet qui ne l’est plus.
Dans la structure proposé par Joa Ebert, on a le modèle suivant :
public static function release( pooledCircle:PooledCircle ):void
{
pooledCircle._next = _currentPooledCircle;
_currentPooledCircle = pooledCircle;
++_availableInPool;
}
public function dispose():void
{
release( this );
}
Ainsi, rendre disponible un objet se fera par l’appel de la méthode dispose() (public) de celui ci.
Ex : pooledCircle.dispose(); // Soit pooledCircle une instance renvoyée par la méthode create();
Ou par la méthode release (public static) de la classe PooledCircle.
Ex : PooledCircle.release( pooledCircle );
Il s’en suit donc la remise en disposition de l’objet qui vient d’être libérer. La valeur de _currentPooledCircle devient donc égale à pooledCircle de nos exemples précédents.
L’ancienne valeur de _currentPooledCircle est attribuée à pooledCircle.next();
Le schéma représentatif :

Suppression des objets d'une liste via la méthode dispose() ou release()
En 1), l’état de la liste après 3 appels de la méthode create().
En 2), l’état de la liste après un appel de la méthode release( pooledCircle:PooledCircle ) ou dispose() (cf plus haut), sur le second objet qui a été récupéré via la méthode create().
En 3), l’état de la liste après un autre appel de la méthode release( pooledCircle:PooledCircle ) ou dispose(), sur le premier objet qui a été récupéré via la méthode create().
Il faut donc bien noter qu’on ne supprime pas un objet, on le rend de nouveau disponible (ne pas oublier de libérer les références de l’ancienne vie de l’objet, pour libérer la mémoire et éviter les comportements chelou au moment d’une nouvelle attribution).
4) Conclusion
Il est donc intéressant de passer par une liste d’objets de la sorte, notamment lorsqu’un certain nombre d’objets doit être crée.
Tweensy, moteur de tween, utilise d’ailleurs une structure d’Objet Pooling, couplé a un Array pour pouvoir parcourir la liste des objets crées.
C’est aussi ce que j’ai souhaité faire avec M4Tween, couplé a une linked list (plus intéressant d’itérer de cette manière qu’à travers un tableau). Mais je dois revoir quelques trucs sur la classe de M4Tween…
Je pense que je posterai prochainement sur les LinkedList.
En attendant, vous trouverez pas mal d’infos à ce sujet sur wikipedia et encore et toujours sur le wiki de Joa Ebert. A noter que le wiki de Joa Ebert ne propose qu’une représentation unique d’une LinkedList. Vous verrez sur wikipedia qu’il en existe plusieurs
Si des choses ne sont pas claires, n’hésitez pas !
M4Tween premier jet
13th juillet, 2009 par FlozJ’ai un peu mis en pause mon application en flex de peinture avec les rubans. J’y reviendrai surement plus tard : j’ai quelques projets en flash avant.
Quand on était à HETIC, on discutait avec Arno de mettre les mains soit dans un moteur de physique/détection de collisions, soit dans un moteur de tween. Le moteur de tween étant plus simple à appréhender seul, du moins je pense, je me suis mis la tête dedans depuis une semaine.
Je suis pas mal le blog de Joa Ebert, notamment pour tout ce qui concerne l’optimisation des performances. J’en parlais dans mon post sur les LinkedList d’ailleurs. Il écrivait qu’il y avait fortement moyen d’obtenir de meilleurs perfs en utilisant une structure d’objet pooling, et des linkedList, au lieu de passer par des Array.
C’est donc ce que j’ai voulu faire, puisqu’il a dit que c’était bien, et qu’en plus il l’avait testé pour obtenir un résultat… impressionnant ? (son moteur de tween chez Hobnox contre tweenlite).
Bon, une chose est sure : la magie a moins (beaucoup moins ?) bien fonctionné de mon côté…
En tout cas, j’ai pas mal appris en terme de réflexion, ainsi que sur l’utilisation des Object, des Linked List, et des Pools. Je pense d’ailleurs faire mon prochain post sur ce sujet, ça pourrait être intéressant (et plus clair que celui que j’avais fais pour l’effet de désintégration !).
Voilà ce que ça donne (attention, 1000 objets qui bougent. Cliquez sur le swf pour lancer, et recliquez pour arrêter ;))
Avec Tweenlite :
Avec Tweener :
Avec M4Tween :
Du côté des résultats, j’ai ici 32/33 pour TweenLite et 36/37 pour M4Tween. Donc c’est assez similaire, et beaucoup moins ouf de ce que Joa Ebert à fait (gneuh). Les FPS oscille beaucoup aussi je trouve sur M4Tween…
Bon, le but n’est pas de remplacer TweenLite (M4Tween peut juste bouger le bordel, il n’y a pas de plugins pour faire des transitions de couleurs etc.), mais d’avoir un moteur de tween interne au framework minuit4, pour nos propres outils.
Donc là, je me sors un peu la tête de ce code. Je verrais dans quelques jours si je vois pas des choses à changer/une partie de la structure à revoir.
Arno pense le porter en JS, donc il y verra surement des choses à revoir.
En ce qui concerne son utilisation :
Pour créer une Tween : M4Tween.create( l’objet sur lequel appliquer la tween, la durée de la tween en secondes, { x: la valeur, y: la valeur, …, easing: Quad.EaseIn, delay: la durée en seconde, onComplete : nomdefonction, onCompleteParams: [ param1, param2, ... ] });
Pour supprimer une Tween : M4Tween.release( laTween );
Pour supprimer une Tween (alternative) : laTween.dispose();
Pour supprimer une Tween d’un objet : M4Tween.releaseTweenOf( l’objet sur lequel a été appliqué la tween );
Les commentaires sont légers pour le moment, j’y reviendrai prochainement.
Pour terminer, l’outil de mesure de performance de minuit4 se voit doté d’une nouvelle fonctionnalité : calculer la moyenne des FPS sur la durée !
Télécharger les sources de M4Tween ici.
Télécharger les sources de l’outil de mesure de performances ici.
Le lien vers le SVN de minuit4 ici.


