Présenté dans un précédent billet, la méthode de bulk addition permet d'ajouter en masse des punaises avec une vitesse nettement supérieure à un ajout classique.

A travers cet article nous allons étudier comment est mise en oeuvre cette fonctionnalité, et comment, dans un cadre précis, on peut optimiser cette partie.

For my english friends, here is the english version.

virtual earth_header

Le constat

Dans une récente analyse, les développeurs de Redfin, portail immobilier important aux Etats-Unis, ont choisi de passer de Virtual Earth à Google Maps pour des raisons de performances.

Après discussions, nous avons pu connaître la technique utilisée par ces derniers et qui consiste à recréer une classe dérivant de GOverlay qui remplacera les markers de base.

Dès lors, il était important dans un cadre formel mais aussi dans l'esprit "développeur curieux", de voir ce qu'il était possible de faire avec VE et comparer les résultats.

Tout ceci en vue de faire une remontée positive et des propositions utiles.

 

Ce qui est améliorable

A notre échelle, on peut difficilement agir sur la compression des scripts, des éléments graphiques et autres éléments qu'utilise l'API Virtual Earth v6.

Plusieurs remontées auprès de Microsoft vont suivre dans les semaines à venir afin de signaler ce besoin de performance.

Dans le cas de ce mini-prototype résultat de l'analyse du script VE, nous allons simplement voir comment il est possible d'optimiser la partie ajout d'élément sur la carte en partant d'un besoin simple.

 

En dessous du capot ?

Analysons dans un premier temps comment se comporte le script de base dans le cas d'un ajout en masse (bulk addition) de punaises.

Nous utilisons la méthode VEMap.AddShape() avec en paramètre une Array d'objet de type VEShape, voici le code associé de cette méthode :

VEMap.prototype.AddShape = function(b) {
    var a = this.m_velayermanager.VE_LayerManager.GetCollectionByIndex(0);
    a._mapGuid = this.GUID; 
    a.AddShape(b)
};

On peut ainsi voir ce qui est fait :

  • La première ligne utiliser le VELayerManager pour récupérer le layer de base correspondant au fond de carte.
  • La référence vers la Map est passé à l'aide du GUID.
  • L'ajout est traité par la classe VEShapeLayer à travers l'appel de la méthode AddShape() avec les mêmes paramètres.

A travers cette classe, voici la méthode VEShapeLayer.AddShape() :

VEShapeLayer.prototype.AddShape = function(a, b) {
    if (a && typeof a.length != "undefined")
        this.AddShapes(a, b);
    else {
        VEValidator.ValidateObject(a, "_veshape", VEShape, "VEShape");

        if (a._shplayer != null || a.Primitives[0] == null)
            throw new VEException("AddShape", 
"err_invalidargument",
L_ShpExist_text); a._shplayer = this; this.UpdateEntityAnnotation(a); this.AddAnnotation(a, b); if (a.GetVisibility()) a.Show() } };

La partie intéressante ici est le fait que l'on vérifie dans un premier temps le type du paramètre, plus précisément on vérifie s'il s'agit d'une array.
Dans notre cas il s'agit d'une array, il est donc nécessaire d'inspecter la méthode VEShapeLayer.AddShapes().

Avec cette méthode on arrive dans le vif du sujet :

VEShapeLayer.prototype.AddShapes = function(c) {
    VEValidator.ValidateObjectArray(c, "_veshapeArray", 
VEShape, "VEShape Array"); var h = c.length, g = GetVEMapInstance(this._mapGuid); if (g && g.GetMapMode() == Msn.VE.MapActionMode.Mode3D) for (var a = 0; a < h; ++a) this.AddShape(c[a ]); else { if (this._index == 0 && this.GetShapeCount() == 0) this.Show(); var d = [], f = this.Annotations.length; for (var a = 0; a < f; a++) this.Annotations[a ].ClearDomElements(); if (!Msn.VE.Environment.IsIE50()) for (var a = 0; a < f; a++) { var b = this.Annotations[a ]; if (b._isDrawn) { if (b.IconUrl == null) b.IconUrl = Msn.VE.API.Constants.iconurl; d.push(b.ToHtml()) } } for (var a = 0; a < h; ++a) { var b = c[a ]; b._shplayer = this; this.UpdateEntityAnnotation(b); this.AddAnnotation(b); if (b.IconUrl == null) b.IconUrl = Msn.VE.API.Constants.iconurl; d.push(b.ToHtml()); b._isDrawn = true } var e = $ID(this.GetId()); if (e) if (Msn.VE.Environment.IsIE50()) e.innerHTML += d.join(""); else e.innerHTML = d.join("") } this.Cluster() };

En effet, on peut remarquer les différents tests effectués sur les paramètres. L'extraction des informations contenues dans ces objets.
Et si on y regarde de plus près, on peut remarquer que le rendu des éléments de type VEShape, est opéré à travers une méthode VEShape.ToHtml().

Ce que l'on modifie

C'est précisément cette méthode que nous allons modifier afin d'ajouter une simple DIV plutôt que plusieurs éléments, conformément à nos besoins.

Ainsi voici le code à ajouter dans le script de notre page afin de modifier le comportement du script de base :

VEShape.prototype.ToHtml = function() {
  var a = [], b = GetVEMapInstance(this._shplayer._mapGuid);

  if (b && b.vemapcontrol) {
    var c = b.vemapcontrol.GetPushpinMapPixel(
      new Msn.VE.LatLong(this.Latitude, this.Longitude),
      b.vemapcontrol.GetZoomLevel()
    );

    var idPin = (this.Primitives[0].type == VEShapeType.Pushpin ? 
this.Primitives[0].iid :
Msn.Drawing.GetLabelUID(this.Primitives[0].iid)); a.push('<div id='); a.push(idPin); //STYLE
a.push(' style="z-index:'); a.push(this.GetZIndex()); a.push(";width:25px;height:25px;background-image:url("); a.push(Msn.VE.API.Constants.iconurl); a.push(");position:absolute;left:"); a.push(c.x - 25 / 2); a.push("px;top:"); a.push(c.y - 25 / 2); //EVENTS
a.push('px;" onclick="alert(\''); a.push(idPin.toString()); // a.push('\');" onmouseover="VEShowVEShapeERO(\''); a.push(this.Primitives[0].iid); a.push('\',\''); a.push(this._shplayer._mapGuid); a.push('\');" onmouseout="VEHideVEShapeERO(false);">');
a.push("</div>"); } if (debugView == true) { alert(a.join("")); debugView = false; } return a.join("") };

Les résultats

Voici un tableau représentant les résultats observés (en millisecondes) :

  Ajout classique Bulk Addition Bulk Addition Opt.
IE7 - 500 pins 2350 ms 751 ms 640 ms
IE7 - 1000 pins 5900 ms 1740 ms 1350 ms
FF3 - 500 pins 710 ms 345 ms 310 ms
FF3 - 1000 pins 2725 ms 1690 ms 1360 ms

La solution Bulk Addition Optimisé présente des performances supérieures à l'ajout classique et à la méthode de bulk addition d'origine.

image

Le gain observé ici :

  • Par rapport à la méthode classique :
    • Avec IE : 300% de performances gagnées
    • Avec FF : 110% de gain
  • Par rapport à la méthode Bulk Addition existante :
    • Avec IE : 20-25% de gain
    • Avec FF : 15-20% de gain

Pour info, sur 10k éléments, sous IE je suis à 55s en bulk, et à 48s en optimisé.

Notes importantes

Cette méthode d'overriding de la méthode de rendu HTML des objets VEShape n'est pas supporté officiellement par Microsoft et peut ne plus fonctionner dans les prochaines versions.

Il s'agit donc bel et bien d'un Proof of Concept qui est présent uniquement pour prouver qu'il est possible d'aller encore plus bas dans l'API et gagner quelques précieuses millisecondes.

D'autant plus que certains effets de bord peuvent se faire sentir concernant l'ajout d'autre élément de type VEShape (les polygons et polylines notamment). Je n'ai très sincèrement pas pris le temps de faire le tour de tous les scénarios possibles.

Enfin, Microsoft recommande de ne pas dépasser 500 éléments affichés sur la carte. C'est une recommandation que je conseille de garder en tête sous peine de perte de stabilité du navigateur et de temps de réponse. Au délà il faut utiliser des techniques de clustering qui seront abordées plus tard.

 

Conclusion

Ce petit plongeon dans le coeur de l'API Virtual Earth permet d'observer des points sur lesquels nous pouvons éventuellement proposer quelques suggestions.

Je pense notamment aux possibilités de customisation des punaises, je reviens souvent sur ce point, mais je les trouve vraiment archaïque comparées à celles de l'API Google Maps. Notamment la gestion des ombres et les offsets.

Je remercie Aurélien, un véritable guru du JavaScript, qui m'a donné une piste de recherche pour le gain de génération en composant le HTML dans une string Array et en accèdant une seule fois au DOM.
C'est déjà la technique utilisée dans l'API dans la plupart des traitements ce qui prouve la justesse de la proposition et la qualité de l'API.

D'autres posts moins techniquement avancés (ou pas ;p) sur VE arriveront, de même que d'autres infos sur Virtual Earth.