Zanbench : Release 0.2
Zanbench, framework actionscript 3 de test de performances est en version 0.2.
Vous pouvez lire l'introduction ou le démarrage rapide, télécharger un package comprenant les sources, le swc, la documentation et les exemples ou vous rendre sur la page d'accueil du projet.
Zanbench : démarrage rapide
Zanbench est un framework actionscript 3 de test de performances. Pour une introduction préliminaire, consultez cet article.
Dans cet article, nous allons voir comment installer et utiliser zanbench.
Installez zanbench
Téléchargez le swc depuis cette adresse et ajoutez le au classpath de votre projet.
Paramétrez le compilateur
Ajoutez la ligne ci-dessous aux options du compilateur. Attention, zanbench utilise la reflexion et ne peut fonctionner sans que le metatag [Benchmark] ne soit retenu après compilation.
-keep-as3-metadata+=Benchmark
Créez un BenchmarkCase
package gettingStarted
{
import com.zanshine.benchmark.core.BenchmarkCase;
public class SimpleBenchmarkCase extends BenchmarkCase
{
}
}
Créez un premier test
Créez une méthode publique, choisissez n'importe quel nom et taggez là avec [Benchmark]
package gettingStarted
{
import com.zanshine.benchmark.core.BenchmarkCase;
import flash.utils.getTimer;
public class SimpleBenchmarkCase extends BenchmarkCase
{
[Benchmark]
public function measureGetTimer():void
{
var whatTimeIsItPlease:Number = getTimer();
}
}
}
Créez une suite, ajoutez le benchmark à cette suite et lancez !
La méthode addBenchmark ci dessous prend comme argument :
- un objet implémentant Benchmarkable, ce qui est le cas de BenchmarkCase qui est la superclasse de SimpleBenchmarkCase
- un entier indiquant le nombre d'itérations de la boucle qui va appeller chaque méthode de test
- un entier indiquant le nombre de répétitions de la boucle
- un entier indiquant le délai en millisecondes entre chaque répétition
package
{
import simple.SimpleBenchmarkCase;
import com.zanshine.benchmark.core.BenchmarkSuite;
import com.zanshine.benchmark.print.ResultPrinter;
import flash.display.Sprite;
public class GettingStartedRunner extends Sprite
{
public function GettingStartedRunner()
{
var suite:BenchmarkSuite = new BenchmarkSuite();
suite.addBenchmark(new SimpleBenchmarkCase(), 1000, 500, 50);
var printer:ResultPrinter = new ResultPrinter(suite);
suite.run();
}
}
}
Les résultats sont visibles dans la console de sortie
benchmark case name => simple::SimpleBenchmarkCase test method name => measureGetTimer message => iterations per loop => 1000 loop run count => 100 delay between loops => 50 raw duration => 381
Ajoutez un second test au BenchmarkCase, décidez de l'ordre d'exécution et ajoutez un message par test
Zanbench offre la possibilité de définir l'orde dans lequel les méthodes de test doivent être appelées. Il est également possible de passer un message pour chaque méthode.
package gettingStarted
{
import com.zanshine.benchmark.core.BenchmarkCase;
import flash.utils.getTimer;
public class SimpleBenchmarkCase extends BenchmarkCase
{
[Benchmark(order="2", message="This test will run in second")]
public function measureGetTimer():void
{
var whatTimeIsItPlease:Number = getTimer();
}
[Benchmark(order=1, message="This test will run first")]
public function measureDate():void
{
var whatTimeIsItPlease:Number = (new Date()).time;
}
}
}
benchmark case name => simple::SimpleBenchmarkCase test method name => measureDate message => This test will run first iterations per loop => 1000 loop run count => 100 delay between loops => 50 raw duration => 713 benchmark case name => simple::SimpleBenchmarkCase test method name => measureGetTimer message => This test will run in second iterations per loop => 1000 loop run count => 100 delay between loops => 50 raw duration => 388
Implémenter Benchmarkable si votre classe ne peut pas hériter de BenchmarkCase
Si pour une raison ou une autre, votre classe de benchmark ne peut pas hériter de BenchmarkCase, il vous suffit d'implémenter l'interface Benchmarkable, ce qui est très simple :
package gettingStarted
{
import com.zanshine.benchmark.core.Benchmarkable;
public class SharedObjectCase implements Benchmarkable
{
public function prepare():void
{
}
public function setUp():void
{
}
public function tearDown():void
{
}
public function clean():void
{
}
}
}
Il n'y a aucune implémentation obligatoire dans chacune de ces quatre méthodes. Vous pouvez les laisser vides et lancer le runner. Ces quatre méthodes sont des callbacks, des méthodes qui sont appelées automatiquement.
Définir des actions spécifiques à des moments clés de l'exécution du Benchmark
Zanbench prend en charge six callbacks différents :
- prepare():void : appelée à l'initialisation du Benchmarkable
- setUp():void : appelée avant chaque méthode de test
- beforeMethodName():void où le litéral "MethodName" correspond à une méthode de test methodName():void : appelée AVANT la méthode methodName():void
- afterMethodName():void où le litéral "MethodName" correspond à une méthode de test methodName():void : appelée APRES la méthode methodName():void
- tearDown():void appelée après chaque méthode de test
- clean():void appelée après toute les méthodes de test et les callbacks
Ces méthodes servent à manipuler, instancier ou détruire des fixtures. Pour ceux qui ne sont pas à l'aise avec les notions de base des framework xUnit, une fixture est un ensemble de préconditions et d'états nécessaires à l'exécution d'un test. Il s'agit du contexte d'exécution du test.
Voyons un exemple illustrant l'usage de ces callbacks (c'est bien sur un exemple didactique, sans prétentions architecturales :) ). Nous voulons mesurer la différence de temps d'exécution entre l'écriture d'une collection de Person sur un SharedObject local et un distant. D'abord, les deux classes du contexte :
package gettingStarted
{
public class Person
{
public function Person()
{
People.registerPerson(this);
}
public var name:String;
public var lastLogginDate:Date;
}
}
package gettingStarted
{
import flash.utils.Dictionary;
public class People
{
private static var people:Dictionary = new Dictionary();
public static function registerPerson(person:Person):void
{
people[person.name] = person;
}
public static function shotEveryOne():void
{
people = new Dictionary();
}
}
}
Voici la classe de test, les commentaires explicatifs sont ci-dessous.
package gettingStarted
{
import flash.net.NetConnection;
import flash.net.SharedObject;
import com.zanshine.benchmark.core.Benchmarkable;
public class SharedObjectCase implements Benchmarkable
{
private var so:SharedObject;
private var connexion:NetConnection;
private var contacts:Array;
public function prepare():void
{
connexion = new NetConnection();
connexion.connect("rtmp://somedomain.com/applicationName");
}
public function setUp():void
{
var paul:Person = new Person();
paul.name = "Paul";
paul.lastLogginDate = new Date();
var luc:Person = new Person();
luc.name = "Paul";
luc.lastLogginDate = new Date();
contacts = [];
contacts.push(paul);
contacts.push(luc);
}
public function tearDown():void
{
so.clear();
}
public function clean():void
{
People.shotEveryOne();
}
/**
* Measuring remote SharedObject writing time
*/
public function beforeRemoteSharedObject():void
{
so = SharedObject.getRemote("sharedObjectCase");
so.connect(connexion);
}
[Benchmark(message="remote shared object writing", order=2)]
public function remoteSharedObject():void
{
writeSO();
}
public function afterRemoteSharedObject():void
{
so.close();
}
/**
* Measuring local SharedObject writing time
*/
public function beforeLocalSharedObject():void
{
so = SharedObject.getLocal("sharedObjectCase");
}
[Benchmark(message="local shared object writing", order="1")]
public function localSharedObject():void
{
writeSO();
}
/**
* Generic write So helper
*/
private function writeSO():void
{
for each(var p:Person in contacts)
{
so.data[p.name] = p;
}
}
}
}
Ici, nous avons une classe Person, qui enregistre une référence d'elle même auprès de la classe People dans son constructeur. Instancier Person n'est donc pas sans conséquences, car cela laisse une trace au niveau de la classe People. Si nous avons d'autres tests qui dépendent d'une classe People dont le registre statique est vide, nos tests seront faussés. Nous devons donc veiller à détruire tous les objets Person créés avant de passer à un autre Benchmark, ce que nous faisons dans le callback clean(). Nous utilisons les callbacks "before" spécifiques à chaque méthode de test pour préparer correctement son contexte d'exécution. Après avoir terminé le test pour le shared object distant, nous fermons la connexion dans le callback "after". Puisque l'objet référencé par so change à chaque méthode de test, nous appelons so.clear() dans le callback tearDown() afin de ne laisser de traces du test ni en local, ni sur le serveur.
Zanbench : framework de test de performances
Zanbench est un framework opensource de test de performances ou "benchmark". Le design de zanbench s'inspire des framework xUnit. Le projet est hébergé sur google code et est actuellement en version 0.2. Dans cet article, je fais une brève introduction à ce qu'est un test de performance avant de vous orienter sur le démarrage rapide.
Pourquoi mesurer les performances ?
La mesure des performances, d'un système, d'un programme ou d'un algorithme est généralement liée à la volonté ou la nécessité d'optimiser le code. Je vous rapporte ici une citation que j'aime particulièrement concernant l'optimisation :
Règle 1: Ne pas optimiser.
Règle 2: Ne pas optimiser tant que nous n'avons pas de solutions parfaitement claire et non optimisée.
M. A. Jackson, Principles of Program Design
Toutefois, lorsque vient la phase d'optimisation d'un programme, nous sommes amenés à rechercher les noeuds de performances et à évaluer les différentes implémentations pouvant réduire les temps d'exécution. La mesure objective des performances d'un algorithme étant tributaire du système sur lequel il s'éxecute, on cherche généralement à mesurer sa vitesse d'exécution par rapport à un autre.
Lorsque l'on veut comparer la performance de tel ou tel autre algorithme, on est souvent contraint d'utiliser la méthode brute, à savoir mettre le code à tester dans une boucle for ou while et mesurer la différence de temps entre l'avant et l'après.
Un exemple de test de performance (benchmark) trivial, qui permet de comparer la performance de getTimer() par rapport à (new Date).time :
package simple
{
import flash.display.Sprite;
import flash.utils.getTimer;
public class TimePerfTest extends Sprite
{
public function TimePerfTest()
{
measureGetTimer();
measureDate();
}
public function measureGetTimer():void
{
var before:Number = getTimer();
var whatTimeIsItPlease:Number;
for (var i:int = 0;i < 1000000; i++)
{
whatTimeIsItPlease = getTimer();
}
var after:Number = getTimer();
trace(after - before);//1469
}
public function measureDate():void
{
var before:Number = getTimer();
var whatTimeIsItPlease:Number;
for (var i:int = 0;i < 1000000; i++)
{
whatTimeIsItPlease = (new Date()).time;
}
var after:Number = getTimer();
trace(after - before);//3728
}
}
}
Cette méthode, bien qu'efficace, présente plusieurs inconvénients. Tout d'abord, on peut vite se retrouver avec un block de code ayant un temps d'exécution supérieur à celui autorisé par le flashplayer, levant ainsi une erreur :
1502 Un script a été exécuté au-delà du délai d'expiration par défaut de 15 secondes
puis :
1503 Un script ne s'est pas arrêté après 30 secondes et a été arrêté.
Même si on évite ces erreurs en cassant la boucle entre plusieurs méthodes, il reste toujours le problème de l'usage des ressources systèmes. Une utilisation trop gourmande des ressources système, notamment processeur et mémoire vive, peut gravement nuire à la qualité des tests.
Si le système donne la priorité à un autre processus alors qu'il tourne à plein régime, les temps d'exécution de l'algorithme seront allongés de manière non représentative. De même, si le système se retrouve obligé de faire une lecture ou une écriture en mémoire virtuelle, du fait d'une utilisation trop importante de la mémoire vive par le flashplayer, les temps d'exécutions seront eux aussi affectés.
Effectuer des benchmarks crédibles demande donc de ménager le système, tout en répétant l'algorithme suffisamment de fois pour avoir un résultat représentatif. Finalement, cette dernière problématique peut aussi être contournée avec un peu de bon sens lors de l'écriture des tests, mais le code nécessaire devient vite redondant et difficile à maintenir.
Zanbench à pour objectif de permettre au développeur de s'abstraire du problème de l'écriture du test de performance, pour se focaliser sur le code à tester lui même. Quelques lignes de codes valant mieux qu'un long discours, rentrons dans le vif du sujet avec ce démarrage rapide.