class: center, middle # Les micro-benchmarks ## ou comment ne rien tester --- class: center, middle .awfy[![Centered image](img/awfy.png)] ??? - Un benchmark est un programme court qui permet de tester la performance d'un langage / framework / interpréteur, etc. - AreWeFastYet vérifie les performances des benchmarks JS dans plusieurs navigateurs. Ces benchmarks ont de grosses charges (prennent du temps à exécuter), sont censés être réalistes mais représentent plutôt des applications web très spécialisées (traitement mathématique, audio, vidéo, etc.). --- class: center, middle ![Centered image](img/firefox.png) ??? - Je suis Benjamin Bouvier et travaille pour Mozilla, dans l'équipe qui implémente le moteur JS de Firefox, Spidermonkey. --- class: center, middle # Comment faire un bon benchmark de JS --- class: middle ```javascript function f() { var before = Date.now(); for (var i = 0; i < 1000000; i++) { var x = document.getElementById("myLittlePony"), y = document.getElementById("myLittleCheval"), z = document.getElementById("myLittleUnicorn"), w = document.getElementById("myLittleDoge"); x.innerHTML = '
Pink fluffy unicorns dancing on rainbow.
'; y.style = 'color: white;'; // etc } console.log(Date.now() - before); } f(); ``` ??? - pour faire un benchmark, créer une boucle qui va lancer du code de nombreuses fois, et diviser le temps pris par le nombre de tours de boucle pour avoir une idée du temps pris par tour de boucle. Lancer au moins 10000 fois. - qu'est ce qui ne va pas dans ce benchmark ? --- background-image: url(img/gameover.jpg) class: bottom right --- class: middle center # Leçon 0 : Faire un benchmark en JS ??? - Ce benchmark teste le DOM (document.getElementById), le layout/rendering (innerHTML), le CSS (style), mais pas le JS en soi. - exemples de choses qui ne sont pas JS (indexeddb, DOM, CSS, workers...) - exemples de choses qui sont du pur JS (chaines de caracteres, fonctions, opérations arithmétiques, ES6...) - jsperf porte mal son nom --- class: middle ```javascript function f() { var before = Date.now(); for (var i = 0; i < 1000000; i++) { var x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; } console.log(Date.now() - before); } f(); ``` ??? - qu'est ce qui va se passer - les JITs vont intervenir - inference de types - compilation dans differents tiers d'execution - compilation dans IonMonkey (dernier tiers d'exécution) --- class: middle center # Compilation .italic[à la volée] 101 --- background-image: url(img/alavolette.jpeg) ??? Attention, j'ai bien dit a la volee, pas a la volette. --- class: middle center # Global Value Numbering ??? Phase d'optimisation qui consiste à assigner des identifiants uniques à toutes les valeurs (dépendents de leurs entrées) et retrouver des valeurs qui pourraient exister en double. --- class: middle ```javascript function f() { var before = Date.now(); for (var i = 0; i < 1000000; i++) { var x = typeof "str"; x = typeof "str"; // copie du x précédent x = typeof "str"; // copie du x précédent x = typeof "str"; // etc. x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; x = typeof "str"; } console.log(Date.now() - before); } f(); ``` ??? "str" reçoit un hash unique, typeof "str" aussi du coup => redondances, d'où possibilité d'élimination. --- class: middle ```javascript function f() { var before = Date.now(); for (var i = 0; i < 1000000; i++) { var x = typeof "str"; } console.log(Date.now() - before); } f(); ``` ??? La VM vient de supprimer 9 des dix opérations à l'intérieur de la boucle. --- class: middle center # Constant Folding ??? Remplacer les opérations constantes par leur résultat direct (remplacer 1 + 1 par 2, par exemple). Tout ce qui n'est pas fait à l'exécution est du temps gagné, donc autant le faire à la compilation, si c'est possible. --- class: middle ```javascript function f() { var before = Date.now(); for (var i = 0; i < 1000000; i++) { var x = "string"; } console.log(Date.now() - before); } f(); ``` ??? typeof "str" == "string" dans tous les cas, on peut donc le remplacer ici. --- class: middle center # Loop Invariant Code Motion ??? A l'intérieur d'une boucle, ce qui ne dépend pas de la boucle peut être calculé une fois avant la boucle, plutôt qu'à chaque tour de boucle ("hoisting"). --- class: middle ```javascript function f() { var before = Date.now(); var x = "string"; // HOISTED for (var i = 0; i < 1000000; i++) { } console.log(Date.now() - before); } f(); ``` ??? Ici, var x = "string" peut être calculé une seule fois, en dehors de la boucle. --- class: middle center # Dead Code Elimination ??? Le code qui n'est pas utilisé, et qui même enlevé, n'est pas remarqué, peut être tout simplement supprimé (code "mort"). --- class: middle ```javascript function f() { var before = Date.now(); for (var i = 0; i < 1000000; i++) { } console.log(Date.now() - before); } f(); ``` ??? var x = "string" n'est pas utilisé, on peut donc l'enlever. --- # Bravo ! Vous venez de chronométrer une boucle vide ! ![image](img/congrats.jpg) --- # Comment rendre ce µ-benchmark obsolète Inférer et éliminer les boucles vides ? ??? Certaines VMs sont déjà capables d'éliminer les boucles vides. --- # Essayons de mesurer quelque chose ```javascript function f(arr) { var before = Date.now(); var result = ['', '', '', '']; for (var i = 0; i < 1000000; i++) { var j = i % 4; result[j] = typeof arr[j]; result[j + 1] = typeof arr[j + 1]; result[j + 2] = typeof arr[j + 2]; result[j + 3] = typeof arr[j + 3]; } console.log(Date.now() - before); return result; } f(['many benchmarks', 'such JS', 'very loopy', 'wow']); ``` ??? - Ne repetez pas les memes operations (copier/coller) - Utilisez des variables qui dependent du compteur de boucle (pour eviter LICM et constant folding) - Utilisez les resultats! - Probleme, vous ne mesurez plus seulement ce que vous mesuriez au depart (ici on mesure aussi le temps que prennent un load/store dans un tableau). --- class: middle center # Conclusion ??? Faire des benchmarks est difficile ! --- class: middle center # Merci de votre attention ## .link[[@njbenji](https://twitter.com/njbenji)] ??? Si vous trouvez des cas où Firefox est considérablement plus lent que Chrome ou Safari ou Internet Explorer sur un micro benchmark JS, merci de nous le signaler (via par twitter directement vers moi @njbenji, en FR ou en EN), soit sur le site https://bugzilla.mozilla.org, ouvrir un nouveau bug (en anglais) dans le Component Core / JavaScript Engine.