Utilisateur:Od1n/Optimisation JavaScript
Performances
modifier- Profiling Firebug, exemple avec la page Cinéma (oui, les durées sont bien pour UN chargement de page) :
Function | Calls | Percent | Own Time | Time | Avg | Min | Max | File |
---|---|---|---|---|---|---|---|---|
addcache | 1 | 21.05 % | 71,653 ms | 112,395 ms | 112,395 ms | 112,395 ms | 112,395 ms | Common.js |
process | 5 | 12.52 % | 42,628 ms | 52,486 ms | 10,497 ms | 1,087 ms | 21,125 ms | Common.js |
loader | 1 | 7,44 % | 25,317 ms | 28,342 ms | 28,342 ms | 28,342 ms | 28,342 ms | (meta) Wikiminiatlas.js |
Sizzle | 21 | 6,35 % | 21,617 ms | 28,105 ms | 1,338 ms | 0,093 ms | 3,629 ms | jquery...?283-13 (line 266) |
runOnloadHook | 2 | 5,76 % | 19,599 ms | 277,454 ms | 138,727 ms | 0,003 ms | 277,451 ms | wikibi...?283-13 (line 985) |
insertAfter | 219 | 5,67 % | 19,309 ms | 27,05 ms | 0,124 ms | 0,11 ms | 1,082 ms | Common.js |
showTocToggle | 1 | 5,52 % | 18,803 ms | 22,831 ms | 22,831 ms | 22,831 ms | 22,831 ms | wikibi...?283-13 (line 132) |
css | 10 | 4,39 % | 14,949 ms | 27,719 ms | 2,772 ms | 0,019 ms | 15,16 ms | jquery...?283-13 (line 351) |
onreadystatechange | 4 | 2,51 % | 8,54 ms | 9,104 ms | 2,276 ms | 0,006 ms | 9,081 ms | index....ascript (line 116) |
curCSS | 25 | 2,16 % | 7,342 ms | 12,892 ms | 0,516 ms | 0,029 ms | 11,184 ms | jquery...?283-13 (line 356) |
updateTooltipAccessKeys | 7 | 1,63 % | 5,552 ms | 6,217 ms | 0,888 ms | 0 ms | 6,194 ms | wikibi...?283-13 (line 254) |
trigger | 54 | 1,56 % | 5,298 ms | 13,481 ms | 0,25 ms | 0 ms | 6,003 ms | jquery...?283-13 (line 137) |
createNavigationBarToggleButton | 1 | 1,45 % | 4,928 ms | 8,867 ms | 8,867 ms | 8,867 ms | 8,867 ms | Common.js |
getElementsByClassName | 3 | 1,44 % | 4,893 ms | 4,893 ms | 1,631 ms | 1,191 ms | 2,405 ms | wikibi...?283-13 (line 466) |
BandeauxPortails_ModifyUl | 1 | 1,33 % | 4,541 ms | 11,876 ms | 11,876 ms | 11,876 ms | 11,876 ms | index....ascript (line 60) |
data | 114 | 1,18 % | 4,028 ms | 4,028 ms | 0,035 ms | 0,005 ms | 2,867 ms | jquery...?283-13 (line 63) |
imageGroup | 1 | 1,07 % | 3,658 ms | 3,658 ms | 3,658 ms | 3,658 ms | 3,658 ms | Common.js |
hasClass | 660 | 1,07 % | 3,627 ms | 3,627 ms | 0,005 ms | 0,001 ms | 0,031 ms | Common.js |
etc. |
Optimisation setModifySectionStyle()
modifierCode actuel (indentation passée de 8 à 4 espaces, parce que bon...) :
/**
* Déplacement des [modifier]
*
* Correction des titres qui s'affichent mal en raison de limitations dues à MediaWiki.
* Ce script devrait pouvoir être supprimé lorsque le [[bugzilla:11555]] sera résolu (comportement équivalent)
*
* Copyright 2006, Marc Mongenet. Licence GPL et GFDL.
*
* The function looks for <span class="editsection">, and move them
* at the end of their parent and display them inline in small font.
* var oldEditsectionLinks=true disables the function.
*/
function setModifySectionStyle()
{
var process = function(list)
{
for(var i=0;i!=list.length;i++)
{
var span=list[i].firstChild
if (span.className == "editsection")
{
span.style.fontSize = "x-small";
span.style.fontWeight = "normal";
span.style.cssFloat = span.style.styleFloat = "none";
span.parentNode.appendChild(document.createTextNode(" "));
span.parentNode.appendChild(span);
}
}
}
try
{
if (!(typeof oldEditsectionLinks == 'undefined' || oldEditsectionLinks == false)) return;
process(document.getElementsByTagName("h2"));
process(document.getElementsByTagName("h3"));
process(document.getElementsByTagName("h4"));
process(document.getElementsByTagName("h5"));
process(document.getElementsByTagName("h6"));
}
catch (e) { }
}
addOnloadHook(setModifySectionStyle);
Première proposition de code, basée sur les remarques en dessous
function setModifySectionStyle(element) { if (typeof oldEditsectionLinks !== 'undefined' && oldEditsectionLinks) { return; } var racine = element ? element : document; try { for (var sections = ["h2", "h3", "h4", "h5", "h6"], i = 0; i < 5; i++) { var list = racine.getElementsByTagName(sections[i]); for (var j = 0, l = list.length; j < l; j++) { var parent = list[j]; var span = parent.firstChild; if (span.className === "editsection") { span.style.fontSize = "x-small"; span.style.cssFloat = span.style.styleFloat = "none"; parent.lastChild.style.marginRight = "0.3em"; parent.appendChild(span); } } } } catch (e) { } } addOnloadHook(setModifySectionStyle);
// désactivation du script actuel var oldEditsectionLinks = true; function setModifySectionStyle_V2(element) { var racine = element ? element : document; try { for (var sections = ["h2", "h3", "h4", "h5", "h6"], i = 0; i < 5; i++) { var list = racine.getElementsByTagName(sections[i]); for (var j = 0, l = list.length; j < l; j++) { var parent = list[j]; var span = parent.firstChild; if (span.className === "editsection") { span.style.fontSize = "x-small"; span.style.cssFloat = span.style.styleFloat = "none"; parent.lastChild.style.marginRight = "0.3em"; parent.appendChild(span); } } } } catch (e) { alert("Exception dans la fonction setModifySectionStyle :\n" + e.message); } } addOnloadHook(setModifySectionStyle_V2);
Seconde proposition de code :
function setModifySectionStyle(element) { if (typeof oldEditsectionLinks !== 'undefined' && oldEditsectionLinks) { return; } var racine = element ? element : document; try { for (var sections = ["h2", "h3", "h4", "h5", "h6"], i = 0; i < 5; i++) { var list = racine.getElementsByTagName(sections[i]); for (var j = 0, l = list.length; j < l; j++) { var parent = list[j]; var span = parent.firstChild; if (span.className === "editsection") { $(parent).addClass("modifiedSectionTitle"); parent.appendChild(span); } } } } catch (e) { } } addOnloadHook(setModifySectionStyle);
.modifiedSectionTitle .editsection { float: none; font-size: x-small; } .modifiedSectionTitle .mw-headline { margin-right: 0.3em; }
Modifications très intéressantes pour les perfs :
- Le
.style.fontWeight = "normal"
est complètement inutile - Vu que l'ajout du
textNode
" " a un impact important sur les perfs, voir pour procéder plutôt avec un margin latéral
- ok, ça semble jouable un margin-right sur le texte des titres, quelques notes :
- appliquer directement un style inline, car ajouter une nouvelle classe est plus coûteux et alourdirait le common.css
- inconvénient : il risque d'y avoir de légères différences de positionnement par rapport à l'existant (un coup c'est 0.25em, un coup c'est 0.3em...), ce qui n'est pas forcément un inconvénient, c'est juste qu'il faudra choisir la valeur à utiliser
- petit avantage en bonus : quand on sélectionne à la souris le texte du titre, il n'y a plus ce fichu espace à la fin
- Devoir appliquer un marginRight annule pour une grande partie le gain de perfs dû à la suppression du textNode " ", dommage, mais bon ça reste quand même mieux. Et de toute façon, nous sommes bien obligés d'augmenter l'espacement d'une façon ou une autre.
- maquette pour expérimentations :
Utilisateur:Od1n/Bac à sable 4(voir aussi le tableau de valeurs à la fin de cette section)
- maquette pour expérimentations :
- ok, ça semble jouable un margin-right sur le texte des titres, quelques notes :
Autres modifications :
Le try/catch semble inutile
- à titre de sûreté, peut-être ajouter un «
if (!elm.childNode) continue
» pour ainsi ne passer lever d'exception si ensuite tentative d'accès au .className d'un élément inexistant (je n'ai pas encore rencontré ce cas, tous les hX ont au moins un childNode, mais cette présomption me semble un peu risquée)- à la réflexion, je pencherais plutôt pour rester sur le try/catch
- à titre de sûreté, peut-être ajouter un «
if
moins tordu, passe de :
if (!(typeof oldEditsectionLinks == 'undefined' || oldEditsectionLinks == false)) return;
- à :
if (typeof oldEditsectionLinks !== 'undefined' && oldEditsectionLinks) return;
- Au lieu d'invoquer une sous-fonction, on fait :
for (var sections = ["h2", "h3", "h4", "h5", "h6"], i = 0; i < 5; i++) {
var list = document.getElementsByTagName(sections[i]);
// suite
}
- L'avantage, outre un gain de perf très léger, est que c'est la fonction setModifySectionStyle() qui apparaitra dans les profiling
- le recours à
span.parentNode
est inutile vu qu'on connait déjà le parent, c'estlist[j]
- (mais comme
list
est un NodeList, penser à bien mettre en cache les accès)
- (mais comme
Ajout de fonctionnalité :
- Modularisation permettant l'utilisation par des scripts ajax (racine différente de
document
)
Note avant que j'oublie :
- Dans la version "old style", il existe déjà un textNode vide, entre l'editsection et le mw-headline. Voir pour s'amuser un peu avec (le supprimer, le réutiliser... )
Valeurs de margin-right sur le mw-headline pour avoir un espacement semblable au système avec le textNode " " :
Firefox 3.6 | Chrome | IE 8 | Opera | |
---|---|---|---|---|
h2 | 0.25em | 0.3em | 0.25em | 0.25em |
h3 | 0.3em | 0.3em | 0.3em | 0.3em |
h4 | 0.3em | 0.3em | 0.3em | 0.25em |
h5 | 0.3em | 0.35em | 0.3em | 0.3em |
h6 | 0.3em | 0.3em | 0.3em | 0.3em |
Optimisation addcache()
modifier- Utilisateur:Od1n/fonction addcache – pour réf rapide, le nettoyeur :
$j("small.cachelinks").remove();
- Retravail du code de sélection des liens à traiter
- IE : pas de document.getElementsByClassName natif ; le getElementsByClassName "maison" est un désastre pour les perfs, jQuery est bien plus performant.
petit bonus : compatibilité avec les skins autres que Vector et Monobook (vu qu'on ne se cantonne pas à #bodyContent) - autres navigateurs : code basé sur du document.getElementsByClassName natif, que nous allons toutefois hautement optimiser avec une réécriture complète.
remarque : il n'y a plus de vérification tag A et OL, mais cela ne devrait théoriquement pas poser de souci. (conservés grâce à la méthode jQuery)
rappel : document.getElementsByClassName ne retourne pas un array mais un NodeList
- IE : pas de document.getElementsByClassName natif ; le getElementsByClassName "maison" est un désastre pour les perfs, jQuery est bien plus performant.
- et en bonus, quelques corrections de bugs (
i
qui était en globale implicite, setAttribute "class" qui ne fonctionne pas sous IE <= 7)
function addcache() {
var external_links;
if (document.getElementsByClassName) {
external_links = document.getElementsByClassName('external');
} else {
external_links = getElementsByClass('external', document.getElementById("bodyContent"), 'a');
}
var links_count = external_links.length;
for (var i = 0; i < links_count; i++) {
//var chemin = external_links[i].href;
//if (chemin.indexOf("http://wikiwix.com/cache/") === -1 && chemin.indexOf("http://web.archive.org/web/*/") === -1 && chemin.indexOf("wikipedia.org") === -1 && chemin.indexOf("wikimedia.org") === -1 && chemin.indexOf("stable.toolserver.org") === -1) {
var li = external_links[i].parentNode;
/*if (li.className === "noarchive") {
continue;
}*/
var depth = 0;
while ((depth < 3) && (li.tagName !== "OL") && (li.parentNode != null)) {
li = li.parentNode;
depth++;
}
if (li.tagName !== "OL" || !(hasClass(li, 'references'))) {
continue;
}
// manipulation DOM ici
//}
}
}
function addcache() {
function ajoute_lien_cache(link) {
//var chemin = link.href;
//if (chemin.indexOf("http://wikiwix.com/cache/") === -1 && chemin.indexOf("http://web.archive.org/web/*/") === -1 && chemin.indexOf("wikipedia.org") === -1 && chemin.indexOf("wikimedia.org") === -1 && chemin.indexOf("stable.toolserver.org") === -1) {
/*if (link.parentNode.className === "noarchive") {
return;
}*/
// manipulation DOM ici
//}
}
if (document.getElementsByClassName) {
var blocs_references = document.getElementsByClassName('references');
var blocs_references_count = blocs_references.length;
for (var i = 0; i < blocs_references_count; i++) {
var links_in_bloc = blocs_references[i].getElementsByClassName('external');
var links_in_bloc_count = links_in_bloc.length;
for (var j = 0; j < links_in_bloc_count; j++) {
ajoute_lien_cache(links_in_bloc[j]);
}
}
} else {
var external_links = $j('ol.references a.external').get();
var links_count = external_links.length;
for (var k = 0; k < links_count; k++) {
ajoute_lien_cache(external_links[k]);
}
}
}
function addcache() {
var liens_a_traiter = [];
if (document.getElementsByClassName) {
var blocs_references = document.getElementsByClassName('references');
var blocs_references_count = blocs_references.length;
for (var i = 0; i < blocs_references_count; i++) {
var links_in_bloc = blocs_references[i].getElementsByClassName('external');
var links_in_bloc_count = links_in_bloc.length;
for (var j = 0; j < links_in_bloc_count; j++) {
liens_a_traiter.push(links_in_bloc[j]);
}
}
} else {
liens_a_traiter = $j('ol.references a.external').get();
}
var liens_a_traiter_count = liens_a_traiter.length;
for (var k = 0; k < liens_a_traiter_count; k++) {
//var chemin = liens_a_traiter[k].href;
//if (chemin.indexOf("http://wikiwix.com/cache/") === -1 && chemin.indexOf("http://web.archive.org/web/*/") === -1 && chemin.indexOf("wikipedia.org") === -1 && chemin.indexOf("wikimedia.org") === -1 && chemin.indexOf("stable.toolserver.org") === -1) {
/*if (liens_a_traiter[k].parentNode.className === "noarchive") {
continue;
}*/
// manipulation DOM ici
//}
}
}
function addcache() {
var collections = [];
if (document.getElementsByClassName) {
var references = document.getElementsByClassName('references');
var references_count = references.length;
for (var i = 0; i < references_count; i++) {
collections[i] = references[i].getElementsByClassName('external');
}
} else {
collections[0] = $j('ol.references a.external').get();
}
var collections_count = collections.length;
for (var j = 0; j < collections_count; j++) {
var liens = collections[j];
var liens_count = liens.length;
for (var k = 0; k < liens_count; k++) {
//var chemin = liens[k].href;
//if (chemin.indexOf("http://wikiwix.com/cache/") === -1 && chemin.indexOf("http://web.archive.org/web/*/") === -1 && chemin.indexOf("wikipedia.org") === -1 && chemin.indexOf("wikimedia.org") === -1 && chemin.indexOf("stable.toolserver.org") === -1) {
/*if (liens[k].parentNode.className === "noarchive") {
continue;
}*/
// manipulation DOM ici
//}
}
}
}
à voir :
- comparatif perfs sélecteurs jQuery (rappel : varie grandement selon les versions de IE)
- .get() superflu ? petite info, .get(undefined) est un alias vers .toArray()
- gain mise en cache du .length pour les sections références ? (peu nombreux) → on va le faire, car on cherche à encaisser au mieux les plus gros articles, et ils contiennent souvent deux sections ou plus
- voir si on gagne un peu en mettant en cache liens[k] → oui. légère perte si un seul accès (forcément), gain observable à partir de 2 accès, et ça s'améliore joliment avec le nombre d'accès
- important : nommer la function, c'est utile (genre pour pas avoir « (?) » dans le profiler)
petit benchmark rapide :
Firefox 3.6 | Opera | Chrome | IE 8 | |
---|---|---|---|---|
nb itérations | 600 | 2000 | 2000 | 30 |
Code origine | 1411 | 1940 | 3265 | 3453 |
Code retravaillé 1 | 916 | 348 | 336 | 63 |
Code retravaillé 2 | 907 | 397 | 335 | 62 |
Code retravaillé 3 | 744 | 6 | 5 | 63 |
Sous Fx, le goulot d'étranglement se situe au niveau des accès propriétés .length des NodeList. edit : nan, pas seulement...
Le code 2 tourne un peu moins bien sur Opera, mais il tourne mieux sur tous les autres navigateurs, et notamment sur les moins performants. de toute façon il y a désormais la 3e méthode, qui met tout le monde d'accord
autre remarque, j'ai l'impression que les perfs du code 1 sous Fx se dégradent au fil du temps (succession de tests, pourtant bien isolés avec un module pattern). gestion mémoire foireuse ? pareil
rappel : ne pas oublier de considérer la précision du compteur, environ 15 ms
Loader
modifierCode actuel :
addOnloadHook(function () {
if (wgNamespaceNumber == 0) {
if ((typeof no_external_cache !== "undefined") && (no_external_cache)) {
return;
}
addcache();
}
function addcache() {
// blah
}
});
... La syntaxe peut prêter à confusion : le hoisting remonte la déclaration de fonction, donc elle est déclarée même si les tests en amont envoient sur le return
. Autrement dit, le code suivant est strictement équivalent :
addOnloadHook(function () {
function addcache() {
// blah
}
if (wgNamespaceNumber == 0) {
if ((typeof no_external_cache !== "undefined") && (no_external_cache)) {
return;
}
addcache();
}
});
trois solutions :
- soit on reste en déclaration classique, la fonction sera alors toujours définie même si le module est désactivé
- soit on passe en déclaration par expression (
var addcache = function () {...}
). l'avantage est que l'on économise la déclaration si le module est désactivé. l'inconvénient est que ce mode de déclaration est légèrement moins performant sous Fx. - soit on met directement le corps de la fonction et on ne passe pas par cette déclaration supplémentaire a priori superflue. La fonction addcache
étantétait enclosée dans l'anonymous du hook, donc elleestétait de toute façon inaccessible aux autres scripts.
done : onloadhook seulement si main namespace. pas d'overhead de function imbriquée lorsque le module est activé (c'est le plus important). lorsque le module est désactivé, la function est déclarée, mais elle n'est pas exécutée, alors bon...
Résultat
modifierCode final
modifierLa méthode avec les getElementsByClassName natifs est très performante pour rapatrier les données, mais celles-ci sont plus lourdes à traiter (p... de NodeList !)
Bien que la méthode suggérée pour l'instant soit déjà plus performante que la méthode actuellement en production, on devrait pouvoir faire mieux. Sur une partie de mes tests, j'ai été induit en erreur par les NodeList (très lents à l'accès, vu qu'ils sont réactualisés à chaque fois). J'expérimente diverses solutions à base de Array.prototype.slice.call()
(attention, soucis de compatibilité), querySelectorAll() et autres joyeusetés. Voire tout simplement jQuery seul, qui en fait ne tourne pas trop mal.
Le problème est que la solution la plus performante s'avère être différente pour chaque navigateur.
/** * application de [[Wikipédia:Prise de décision/Système de cache]] * un <span class="noarchive"> autour du lien l'empêche d'être pris en compte * pour celui-ci uniquement * un no_external_cache=true dans un monobook personnel désactive le script */ if (wgNamespaceNumber === 0) { addOnloadHook(function addcache() { if (typeof no_external_cache !== "undefined" && no_external_cache) { return; } var collections_count; var collections = []; if (document.getElementsByClassName) { var references = document.getElementsByClassName('references'); collections_count = references.length; for (var i = 0; i < collections_count; i++) { collections[i] = references[i].getElementsByClassName('external'); } } else { collections_count = 1; collections[0] = $j('ol.references a.external').get(); } for (var j = 0; j < collections_count; j++) { var liens = collections[j]; var liens_count = liens.length; for (var k = 0; k < liens_count; k++) { var lien_en_cours = liens[k]; var chemin = lien_en_cours.href; if (chemin.indexOf("http://wikiwix.com/cache/") > -1 || chemin.indexOf("http://web.archive.org/web/*/") > -1 || chemin.indexOf("wikipedia.org") > -1 || chemin.indexOf("wikimedia.org") > -1 || chemin.indexOf("stable.toolserver.org") > -1) { continue; } var element_parent = lien_en_cours.parentNode; if (element_parent.className === "noarchive") { continue; } var titre = getTextContent(lien_en_cours); var last = document.createElement("small"); last.className = "cachelinks"; last.appendChild(document.createTextNode("\u00a0[")); var link = document.createElement("a"); link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre)); link.setAttribute("title", "archive de " + titre); link.appendChild(document.createTextNode("archive")); last.appendChild(link); last.appendChild(document.createTextNode("]")); element_parent.insertBefore(last, lien_en_cours.nextSibling); } } }); }
small.cachelinks, small.cachelinks a { color: #36B }
Benchmark
modifier(function () {
function addcache_V1() {
var external_links;
if (document.getElementsByClassName) {
external_links = document.getElementsByClassName('external');
} else {
external_links = getElementsByClass('external',document.getElementById("bodyContent"),'a');
}
for( var i = 0;i < external_links.length;i++)
{
var chemin = external_links[i].href;
if(chemin.indexOf("http://wikiwix.com/cache/")==-1 && chemin.indexOf("http://web.archive.org/web/*/")==-1 && chemin.indexOf("wikipedia.org")==-1 && chemin.indexOf("wikimedia.org")==-1 && chemin.indexOf("stable.toolserver.org")==-1)
{
var li = external_links[i].parentNode;
if (li.className == "noarchive") continue;
var depth = 0;
while ((depth < 3) && (li.tagName != "OL") && (li.parentNode != null)) {
li = li.parentNode;
depth++;
}
if (li.tagName != "OL" || !(hasClass(li, 'references')) ) continue;
var titre = getTextContent(external_links[i]);
var last = document.createElement("small");
last.className = "cachelinks";
last.style.color = "#3366BB";
last.appendChild(document.createTextNode("\u00a0["));
insertAfter(external_links[i].parentNode, last, external_links[i]);
var link = document.createElement("a");
link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre));
link.setAttribute("title", "archive de "+ titre);
link.appendChild(document.createTextNode("archive"));
link.style.color = "#3366BB";
last.appendChild(link);
last.appendChild(document.createTextNode("]"));
}
}
}
function addcache_V2() {
var collections_count;
var collections = [];
if (document.getElementsByClassName) {
var references = document.getElementsByClassName('references');
collections_count = references.length;
for (var i = 0; i < collections_count; i++) {
collections[i] = references[i].getElementsByClassName('external');
}
} else {
collections_count = 1;
collections[0] = $j('ol.references a.external').get();
}
for (var j = 0; j < collections_count; j++) {
var liens = collections[j];
var liens_count = liens.length;
for (var k = 0; k < liens_count; k++) {
var lien_en_cours = liens[k];
var chemin = lien_en_cours.href;
if (chemin.indexOf("http://wikiwix.com/cache/") > -1 || chemin.indexOf("http://web.archive.org/web/*/") > -1 || chemin.indexOf("wikipedia.org") > -1 || chemin.indexOf("wikimedia.org") > -1 || chemin.indexOf("stable.toolserver.org") > -1) {
continue;
}
var element_parent = lien_en_cours.parentNode;
if (element_parent.className === "noarchive") {
continue;
}
var titre = getTextContent(lien_en_cours);
var last = document.createElement("small");
last.className = "cachelinks";
last.style.color = "#3366BB"; // à déplacer vers le Common.css : small.cachelinks {color:#36B}
last.appendChild(document.createTextNode("\u00a0["));
var link = document.createElement("a");
link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre));
link.setAttribute("title", "archive de " + titre);
link.appendChild(document.createTextNode("archive"));
link.style.color = "#3366BB"; // à déplacer vers le Common.css : small.cachelinks a {color:#36B}
last.appendChild(link);
last.appendChild(document.createTextNode("]"));
element_parent.insertBefore(last, lien_en_cours.nextSibling);
}
}
}
var r, loop = 10;
$j("small.cachelinks").remove();
var T1 = new Date();
for (r = 1; r <= loop; r++) {
addcache_V1();
}
var T2 = new Date();
$j("small.cachelinks").remove();
var T3 = new Date();
for (r = 1; r <= loop; r++) {
addcache_V2();
}
var T4 = new Date();
$j("small.cachelinks").remove();
console.log(T2 - T1);
console.log(T4 - T3);
})();
Sur l'article Cinéma (218 liens à traiter) – pour 10 itérations :
Firefox 3.6 | Opera | Chrome | IE 8 | IE 7 | |
---|---|---|---|---|---|
vieux code | 1250 | 4110 | 6485 | 2766 | 4203 |
nouveau code | 652 | 1283 | 523 | 1000 | 2140 |
Conclusion : le nouveau code est plus efficace dans toutes les situations. Le benchmark le prouve.
Mise à jour
modifierNouveau code
modifierEn utilisant tout simplement jQuery :
/** * application de [[Wikipédia:Prise de décision/Système de cache]] * un <span class="noarchive"> autour du lien l'empêche d'être pris en compte * pour celui-ci uniquement * un no_external_cache=true dans un monobook personnel désactive le script */ if (wgNamespaceNumber === 0) { addOnloadHook(function addcache() { if (typeof no_external_cache !== "undefined" && no_external_cache) { return; } var liens = $j('ol.references a.external'); for (var i = 0, l = liens.length; i < l; i++) { var lien_en_cours = liens[i]; var chemin = lien_en_cours.href; if (chemin.indexOf("http://wikiwix.com/cache/") > -1 || chemin.indexOf("http://web.archive.org/web/*/") > -1 || chemin.indexOf("wikipedia.org") > -1 || chemin.indexOf("wikimedia.org") > -1 || chemin.indexOf("stable.toolserver.org") > -1) { continue; } var element_parent = lien_en_cours.parentNode; if (element_parent.className === "noarchive") { continue; } var titre = getTextContent(lien_en_cours); var last = document.createElement("small"); last.className = "cachelinks"; last.appendChild(document.createTextNode("\u00a0[")); var link = document.createElement("a"); link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre)); link.setAttribute("title", "archive de " + titre); link.appendChild(document.createTextNode("archive")); last.appendChild(link); last.appendChild(document.createTextNode("]")); element_parent.insertBefore(last, lien_en_cours.nextSibling); } }); }
small.cachelinks, small.cachelinks a { color: #36B }
Nouveau code (encore)
modifierQuelques améliorations :
- Modularisation (la function peut ainsi être invoquée par les scripts ajax)
- Utilisation hasClass() au lieu de .className === "noarchive" (qui peut le plus peut le moins, et principe de moindre surprise)
/** * application de [[Wikipédia:Prise de décision/Système de cache]] * un <span class="noarchive"> autour du lien l'empêche d'être pris en compte * pour celui-ci uniquement * un no_external_cache=true dans un monobook personnel désactive le script */ function addcache(element) { if (typeof no_external_cache !== "undefined" && no_external_cache) { return; } var liens = element ? $j(element + ' ol.references a.external') : $j('ol.references a.external'); for (var i = 0, l = liens.length; i < l; i++) { var lien_en_cours = liens[i]; var chemin = lien_en_cours.href; if (chemin.indexOf("http://wikiwix.com/cache/") > -1 || chemin.indexOf("http://web.archive.org/web/*/") > -1 || chemin.indexOf("wikipedia.org") > -1 || chemin.indexOf("wikimedia.org") > -1 || chemin.indexOf("stable.toolserver.org") > -1) { continue; } var element_parent = lien_en_cours.parentNode; if (hasClass(element_parent, "noarchive")) { continue; } var titre = getTextContent(lien_en_cours); var last = document.createElement("small"); last.className = "cachelinks"; last.appendChild(document.createTextNode("\u00a0[")); var link = document.createElement("a"); link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre)); link.setAttribute("title", "archive de " + titre); link.appendChild(document.createTextNode("archive")); last.appendChild(link); last.appendChild(document.createTextNode("]")); element_parent.insertBefore(last, lien_en_cours.nextSibling); } } if (wgNamespaceNumber === 0) { addOnloadHook(addcache); }
small.cachelinks, small.cachelinks a { color: #36B }
Benchmark
modifier(function () {
function addcache_V1() {
var external_links;
if (document.getElementsByClassName) {
external_links = document.getElementsByClassName('external');
} else {
external_links = getElementsByClass('external',document.getElementById("bodyContent"),'a');
}
for( var i = 0;i < external_links.length;i++)
{
var chemin = external_links[i].href;
if(chemin.indexOf("http://wikiwix.com/cache/")==-1 && chemin.indexOf("http://web.archive.org/web/*/")==-1 && chemin.indexOf("wikipedia.org")==-1 && chemin.indexOf("wikimedia.org")==-1 && chemin.indexOf("stable.toolserver.org")==-1)
{
var li = external_links[i].parentNode;
if (li.className == "noarchive") continue;
var depth = 0;
while ((depth < 3) && (li.tagName != "OL") && (li.parentNode != null)) {
li = li.parentNode;
depth++;
}
if (li.tagName != "OL" || !(hasClass(li, 'references')) ) continue;
var titre = getTextContent(external_links[i]);
var last = document.createElement("small");
last.className = "cachelinks";
//last.style.color = "#3366BB";
last.appendChild(document.createTextNode("\u00a0["));
insertAfter(external_links[i].parentNode, last, external_links[i]);
var link = document.createElement("a");
link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre));
link.setAttribute("title", "archive de "+ titre);
link.appendChild(document.createTextNode("archive"));
//link.style.color = "#3366BB";
last.appendChild(link);
last.appendChild(document.createTextNode("]"));
}
}
}
function addcache_V2() {
var collections_count;
var collections = [];
if (document.getElementsByClassName) {
var references = document.getElementsByClassName('references');
collections_count = references.length;
for (var i = 0; i < collections_count; i++) {
collections[i] = references[i].getElementsByClassName('external');
}
} else {
collections_count = 1;
collections[0] = $j('ol.references a.external').get();
}
for (var j = 0; j < collections_count; j++) {
var liens = collections[j];
var liens_count = liens.length;
for (var k = 0; k < liens_count; k++) {
var lien_en_cours = liens[k];
var chemin = lien_en_cours.href;
if (chemin.indexOf("http://wikiwix.com/cache/") > -1 || chemin.indexOf("http://web.archive.org/web/*/") > -1 || chemin.indexOf("wikipedia.org") > -1 || chemin.indexOf("wikimedia.org") > -1 || chemin.indexOf("stable.toolserver.org") > -1) {
continue;
}
var element_parent = lien_en_cours.parentNode;
if (element_parent.className === "noarchive") {
continue;
}
var titre = getTextContent(lien_en_cours);
var last = document.createElement("small");
last.className = "cachelinks";
//last.style.color = "#3366BB";
last.appendChild(document.createTextNode("\u00a0["));
var link = document.createElement("a");
link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre));
link.setAttribute("title", "archive de " + titre);
link.appendChild(document.createTextNode("archive"));
//link.style.color = "#3366BB";
last.appendChild(link);
last.appendChild(document.createTextNode("]"));
element_parent.insertBefore(last, lien_en_cours.nextSibling);
}
}
}
function addcache_V3() {
var liens = $j('ol.references a.external');
for (var i = 0, l = liens.length; i < l; i++) {
var lien_en_cours = liens[i];
var chemin = lien_en_cours.href;
if (chemin.indexOf("http://wikiwix.com/cache/") > -1 || chemin.indexOf("http://web.archive.org/web/*/") > -1 || chemin.indexOf("wikipedia.org") > -1 || chemin.indexOf("wikimedia.org") > -1 || chemin.indexOf("stable.toolserver.org") > -1) {
continue;
}
var element_parent = lien_en_cours.parentNode;
if (element_parent.className === "noarchive") {
continue;
}
var titre = getTextContent(lien_en_cours);
var last = document.createElement("small");
last.className = "cachelinks";
//last.style.color = "#3366BB";
last.appendChild(document.createTextNode("\u00a0["));
var link = document.createElement("a");
link.setAttribute("href", "http://wikiwix.com/cache/?url=" + chemin.replace(/%/g, "%25").replace(/&/g, "%26") + "&title=" + encodeURIComponent(titre));
link.setAttribute("title", "archive de " + titre);
link.appendChild(document.createTextNode("archive"));
//link.style.color = "#3366BB";
last.appendChild(link);
last.appendChild(document.createTextNode("]"));
element_parent.insertBefore(last, lien_en_cours.nextSibling);
}
}
var r, loop = 10;
$j("small.cachelinks").remove();
var T1 = new Date();
for (r = 1; r <= loop; r++) {
addcache_V1();
}
var T2 = new Date();
$j("small.cachelinks").remove();
var T3 = new Date();
for (r = 1; r <= loop; r++) {
addcache_V2();
}
var T4 = new Date();
$j("small.cachelinks").remove();
var T5 = new Date();
for (r = 1; r <= loop; r++) {
addcache_V3();
}
var T6 = new Date();
$j("small.cachelinks").remove();
console.log(T2 - T1);
console.log(T4 - T3);
console.log(T6 - T5);
})();
Sur l'article Boite de conserve (5 sections Références, 69 liens à traiter en tout), 20 itérations :
Firefox 3.6[1] | Opera | Chrome | IE 8 | |
---|---|---|---|---|
code actuellement en prod | 576 ~ 1189 | 1049 | 1621 | 1593 |
ma 1re proposition de code | 356 ~ 432 | 494 | 193 | 625 |
la nouvelle proposition de code | 369 ~ 442 | 116 | 98 | 625 |
- les résultats du code actuellement en prod sont très variables avec Firefox, ça sent la mémoire mal gérée, espérons que Firefox 4 arrangera ça
Le code de la nouvelle version est beaucoup plus simple. Et les performances sont bien supérieures sous Opera et Chrome. Le benchmark le prouve.
Les résultats sont un poil moins bons sous Firefox, mais c'est très faible et cela changera peut-être avec les futures versions de Firefox. En tout cas, rien ne justifie de conserver le long code précédent (récupérant les éléments "à la main" pour essayer de faire plus vite que jQuery).
J'ajoute que je suis épaté par les perfs de jQuery, et tout particulièrement son moteur de sélection Sizzle.
Liens utiles
modifier- Projet:JavaScript/Développeurs – pour ne pas scanner tout le document mais seulement depuis l'id du contenu de l'article
Divers
modifier// classique :
tableau.push(data);
// plus performant :
tableau[tableau.length] = data;
// et bien sûr :
var i = tableau.length;
tableau[i++] = data;