Il problema di misurare il runtime dell'app Node.js
Misurare il runtime di un'applicazione Node.js è un aspetto critico dell'ottimizzazione delle prestazioni. Misurando il tempo necessario per l'esecuzione di diverse parti della tua applicazione, puoi identificare i colli di bottiglia e ottimizzare il codice per migliorare le prestazioni.
Come possiamo misurare il tempo di esecuzione di una funzione?
Esistono molti modi per misurare il tempo in node.js, il modo più ad alta risoluzione per farlo è utilizzare process.hrtime() o Perf_hooks , entrambi sono migliori rispetto all'utilizzo della classe Date perché non si appoggiano all'orologio di sistema, tuttavia, ci sono poche differenze tra loro:
process.hrtime() fornisce una risoluzione a livello di nanosecondi, mentre perf_hooks fornisce una risoluzione a livello di microsecondi. Ciò significa che process.hrtime() è generalmente più preciso ma anche più costoso da chiamare.
Ora facciamo un esempio di misurazione del tempo di esecuzione della seguente logica. Abbiamo una logica asincrona (possiamo presumere che si tratti di una chiamata HTTP) che si risolve dopo 1000 ms. Useremo perf_hooks per misurare quanto tempo ci vuole per mantenere questa promessa.
const {performance} = require('perf_hooks');
const SOME_BIG_NUMBER = 4000000000;
async function asyncLogic(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const start = performance.now();
asyncLogic(1000).then(() => {
const time = performance.now() - start;
console.log(`Time taken: ${time}ms`);
});
let x = 0;
for (let i = 0; i < SOME_BIG_NUMBER; i++) {
x += Math.sqrt(SOME_BIG_NUMBER);
}
Time taken: 3948ms
Il ciclo di eventi e la coda degli eventi
Il modello di runtime JavaScript si basa sul ciclo di eventi, questa è la parte responsabile dell'esecuzione del nostro codice e ci consente di utilizzare la logica asincrona.
Quando eseguiamo codice JavaScript accade quanto segue:
- JavaScript esegue tutto il codice sincrono nell'ordine in cui appare nel programma.
- Se è presente un codice asincrono (come la nostra funzione asyncLogic), JavaScript lo aggiungerà alla coda degli eventi invece di eseguirlo immediatamente.
- Il ciclo degli eventi controlla costantemente la coda degli eventi per vedere se ci sono eventi in attesa di essere eseguiti.
- Quando un evento è pronto per essere eseguito, il ciclo di eventi lo sposta dalla coda degli eventi allo stack di chiamate , dove può essere eseguito.
- Una volta eseguito l'evento, qualsiasi codice sincrono aggiuntivo viene eseguito prima che il ciclo di eventi torni indietro per controllare la coda degli eventi per ulteriori eventi.
Ecco perché nel nostro esempio, la misurazione del tempo era di 4000ms,
- Eseguiamo la funzione asyncLogic, che è stata aggiunta alla coda degli eventi.
- Quindi eseguiamo la nostra logica sincrona, che richiede circa 4000 ms per l'esecuzione.
- Il ciclo di eventi sposta la nostra chiamata asyncLogic() dalla coda degli eventi allo stack di chiamate
- Quindi eseguiamo la funzione di callback e calcoliamo l'ora di fine
Ritardo ciclo eventi
Pertanto, durante il monitoraggio delle prestazioni dell'applicazione, è necessario prestare attenzione a una metrica importante, il ritardo del ciclo di eventi, il tempo impiegato dal ciclo di eventi tra l'aggiunta di una nuova richiamata alla coda degli eventi e l'esecuzione di questa richiamata.
Possiamo facilmente creare un ciclo che ci permetterà di misurare il nostro ritardo del ciclo di eventi con il seguente codice:
function measureEventLoopLag() {
let lastLoopTime = performance.now();
setTimeout(() => {
const delay = performance.now() - lastLoopTime - 1000;
lastLoopTime = performance.now();
console.log(`Event Loop lag is: ${delay} ms`)
measureEventLoopLag();
}, 1000)
}
Miglioramento dell'autonomia
Supponiamo che il nostro codice abbia una chiamata HTTP che richiede 4000 ms e che abbiamo anche un ciclo lento che impiega circa 4000 ms per essere eseguito. Come abbiamo visto nel nostro esempio precedente, quando misuriamo questo tempo di esecuzione, scopriremo che ci sono voluti 4000 ms per eseguire l'intero codice.
Dopo il refactoring del nostro codice, riduciamo il tempo di esecuzione del ciclo lento del 50%. Quando abbiamo rieseguito il codice, il tempo di esecuzione complessivo in base alla nostra misurazione non è cambiato realmente, abbiamo comunque aspettato circa 4000 ms per completare il flusso complessivo perché aspettavamo che il nostro flusso asincrono fosse completato. Sembra che il nostro refactor non sia stato di grande aiuto.
Non proprio, se diamo un'occhiata in termini di CPU abbiamo fatto un'enorme differenza. Le nostre chiamate HTTP non consumano molta CPU, per lo più aspettiamo solo la risposta. Quindi la maggior parte delle volte, la nostra CPU eseguiva il nostro ciclo lento, quando abbiamo refactoring il codice e lo abbiamo ridotto del 50% non ha cambiato il tempo complessivo ma ha avuto un effetto significativo sulla nostra CPU.
Conclusione
Nel nostro esempio, avevamo una logica molto piccola da monitorare, le cose si complicano davvero quando hai un sistema complesso con molta logica da eseguire, in tal caso dovresti provare a misurare piccole parti della tua logica per monitorare meglio il calo delle prestazioni.
Ci sono due cose importanti da notare quando si monitora il tempo di esecuzione dell'applicazione:
- La misurazione del tempo della funzione asincrona può essere influenzata dal codice sincrono (ritardo del ciclo di eventi)
- Le funzioni di misurazione che hanno una logica sia asincrona che sincrona possono nascondere il miglioramento (o il declino) del runtime.
- Il ritardo del ciclo di eventi superiore a ~ 30 ms è davvero negativo per la tua applicazione.

![Che cos'è un elenco collegato, comunque? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































