Geoscale : passage au xml et performance des requêtes

Geoscale, la création d'une échelle géologique en HTML / CSS / javascript, est un projet sur lequel je travaille trop peu souvent ; je l'avais laissé il y a plus d'un an avec une API et un rendu presque entièrement finalisé. Mais j'ai décidé d'abandonner le JSON comme format de stockage des données pour utiliser du XML.

Pourquoi ce changement alors que j'utilise le JSON partout ailleurs ? Simplement parce qu'une échelle de temps géologique possède une caractéristique fondamentale, la hiérarchisation des étages, et qu'une notion de hiérarchisation se rend mieux et se manipule plus simplement en XML. Avec le JSON, j'étais obligé de manipuler les données à l'initialisation afin de permettre les requêtes en tout point de la hiérarchie ; ce faisant, je créais des notions qui existent « naturellement » dans des données XML via l'API DOM (parent, enfants, frère, etc.).

J'ai donc transformé les données JSON définissant l'échelle géologique en document XML associé à une DTD(1). Et, en adaptant le code à ce nouveau format de donnée, je me suis posé des questions sur les performances de querySelector par rapport aux méthodes plus « traditionnelles » du DOM comme getElementsByTagName. Ainsi, pour récupérer un élément par la valeur d'un attribut, nous avons (au moins) 2 possibilités :

/**
 *  @param {Element} parent Elément parent.
 *  @param {String} nodeName Nom de l'élément descendant à chercher.
 *  @param {String} attrName Nom de l'attribut.
 *  @param {String} attrValue Valeur de l'attribut.
 *  @return {Element | null}
 */
var getByTagName = function(parent, nodeName, attrName, attrValue) {
    var els = parent.getElementsByTagName(nodeName);
    for (var i = 0, n = els.length; i < n; i++) {
        if (els[i].getAttribute(attrName) === attrValue) {
            return els[i];
        }
    }
    return null;
};
Récupérer un élément par la valeur d'un attribut avec getElementsByTagName.
/**
 *  @param {Element} parent Elément parent.
 *  @param {String} nodeName Nom de l'élément descendant à chercher.
 *  @param {String} attrName Nom de l'attribut.
 *  @param {String} attrValue Valeur de l'attribut.
 *  @return {Element | null}
 */
var getByQuerySelector = function(parent, nodeName, attrName, attrValue) {
    return parent.querySelector(nodeName + '[' + attrName + '=' + attrValue + ']');
};
Récupérer un élément par la valeur d'un attribut avec QuerySelector.

Une première évidence saute aux yeux : l'utilisation de querySelector nécessite beaucoup moins de code ; mais cette simplicité ne pèserait pas bien lourd si les performances étaient dégradées.

Dans le cas de geoscale, il est question de récupérer des étages de l'échelle géologique par leur type et leur nom, la structure des données étant la suivante :

<scale>
    <eon name="">
        <era name="">
            <period name="">
                <epoch name="">
                    <age name="" />
                </epoch>
            </period>
        </era>
    </eon>
</scale>
Structure hiérarchique de l'échelle géologique au format xml.

Partant du noeud scale, on doit pouvoir récupérer l'éon « Phanérozoïque », l'ère « Mésozoïque », la période « Jurassique », etc. J'ai donc effectué quelques tests en utilisant les 2 techniques décrites ci-dessus et en mesurant les performances à l'aide de benchmark.js :

getEon('phanerozoic')
getByTagName
213 873 ±1.05%
getByQuerySelector
153 111 ±1.88%
getEra('mesozoic')
getByTagName
160 102 ±4.39%
getByQuerySelector
111 700 ±1.18%
getPeriod('jurassic')
getByTagName
101 424 ±1.73%
getByQuerySelector
97 102 ±1.12%
getEpoch('miocene')
getByTagName
69 684 ±1.80%
getByQuerySelector
78 564 ±1.08%
getAge('maastrichtian')
getByTagName
32 617 ±2.24%
getByQuerySelector
71 781 ±0.73%
Performances des méthodes de récupération de noeud (opérations par seconde)
  • Les requêtes d'un éon ou d'une ère sont environ 1.4 fois plus performantes avec getElementsByTagName qu'avec querySelector, quel que soit l'élément demandé.
  • Les performances sont sensiblement identiques pour les requêtes d'une période ou d'une époque.
  • Les performances des requêtes sur les âges, l'élément le plus « profond » dans l'arborescence xml, varient énormément en fonction de l'âge demandé. Pour certains âges, querySelector est 2 fois plus performant que getElementsByTagName ; pour d'autres, les performances sont sensiblement identiques.