Introduction : pourquoi le profilage est la première étape vers l'optimisation
\nOptimiser le code sans profilage préalable, c'est tirer à l'aveugle. Souvent, les développeurs passent des heures à « améliorer » des parties de code qui ne sont en réalité pas un goulot d'étranglement. Le profilage du code JavaScript permet de déterminer précisément quelles fonctions consomment le plus de temps CPU, où se produisent les fuites mémoire et quelles opérations bloquent la boucle d'événements (Event Loop).
\nDans cet article, nous allons examiner les méthodes pratiques de profilage dans l'environnement Node.js, en utilisant à la fois les outils intégrés et les outils de navigateur. Vous apprendrez à trouver les véritables problèmes de performance, sans avoir à deviner.
\n\n1. Outils pour le profilage Node.js
\nNode.js fournit plusieurs outils puissants, mais souvent sous-estimés. Examinons trois approches principales.
\n\n1.1. Profileur intégré V8
\nLa façon la plus simple d'obtenir un profil est d'exécuter un script avec le drapeau --prof :
node --prof app.js\nUne fois le script terminé, un fichier isolate-0x...-v8.log est créé. Pour le convertir en texte lisible, utilisez :
node --prof-process isolate-0x...-v8.log > profile.txt\nExemple de sortie (simplifié) :
\nStatistical profiling result from isolate-0x3a00000-v8.log, (1002 ticks, 1 unaccounted, 0 excluded).\n\n [Shared libraries]:\n ticks total nonlib name\n 10 1.0% /lib/x86_64-linux-gnu/libc-2.31.so\n\n [JavaScript]:\n ticks total nonlib name\n 450 44.9% 45.5% LazyCompile: *processData /home/user/app.js:12:17\n 200 20.0% 20.2% LazyCompile: *fetchFromDB /home/user/app.js:34:23\n 100 10.0% 10.1% Builtin: CallFunctionReceiver\n\nIci, on voit que la fonction processData consomme près de la moitié du temps — c'est une cible évidente pour l'optimisation.
1.2. Chrome DevTools pour Node.js
\nPour une analyse plus visuelle, utilisez Chrome DevTools. Lancez l'application avec le drapeau --inspect :
node --inspect app.js\nOuvrez chrome://inspect dans Chrome et cliquez sur « Open dedicated DevTools for Node ». Vous verrez les onglets Profiler (ou Performance dans les versions récentes). Enregistrez un profil en effectuant l'action souhaitée dans l'application, puis analysez le Flame Graph (diagramme en flammes).
Le Flame Graph montre quelles fonctions s'appellent les unes les autres et combien de temps elles prennent. Les « barres » larges sont les fonctions les plus coûteuses.
\n\n1.3. console.time et console.timeEnd
\nPour des mesures ponctuelles rapides, utilisez la paire console.time / console.timeEnd :
console.time('parseJSON');\nconst data = JSON.parse(largeString);\nconsole.timeEnd('parseJSON'); // parseJSON: 12.345 ms\nCette méthode ne nécessite pas de lancer un profileur séparé et est idéale pour le débogage local. Cependant, rappelez-vous que les résultats sont influencés par la charge actuelle du CPU, donc effectuez plusieurs mesures.
\n\n2. Exemple pratique : recherche d'un goulot d'étranglement dans un serveur web
\nImaginons que nous ayons un simple serveur HTTP avec Express, qui traite les requêtes, charge des données depuis un fichier et effectue des calculs complexes. Notre tâche est de trouver pourquoi les réponses ralentissent sous charge.
\n\n2.1. Code source (problématique)
\nconst express = require('express');\nconst fs = require('fs');\nconst app = express();\n\napp.get('/data', (req, res) => {\n console.time('total');\n \n // Lecture synchrone du fichier (opération bloquante)\n const fileData = fs.readFileSync('./large-file.json', 'utf8');\n \n // Parsing JSON\n const json = JSON.parse(fileData);\n \n // Transformation complexe\n const result = json.map(item => {\n let sum = 0;\n for (let i = 0; i < 10000; i++) {\n sum += Math.sqrt(item.value * i);\n }\n return { id: item.id, sum };\n })