2012-09-14 9 views
9

Per un progetto imminente con node.js, è necessario eseguire varie attività di manutenzione periodica. In particolare alcuni compiti ogni millisecondo, altri ogni 20 ms (50 volte al secondo) e altri ancora ogni secondo. Così ho pensato di usare setInterval(), con risultati divertenti: molte chiamate di funzione venivano saltate.node.js: setInterval() per saltare le chiamate

Il riferimento I utilizzata è la seguente:

var counter = 0; 
var seconds = 0; 
var short = 1; 
setInterval(function() { 
     counter ++; 
    }, short); 
setInterval(function() { 
     seconds ++; 
     log('Seconds: ' + seconds + ', counter: ' + 
      counter + ', missed ' + 
      (seconds * 1000/short - counter)); 
    }, 1000); 

C'è un timer lungo di un secondo e un breve che può essere regolata utilizzando la variabile short, in questo caso 1 ms. Ogni secondo viene stampata la differenza tra il numero di tick previsti nel ciclo breve e il numero effettivo di volte in cui il contatore breve è stato aggiornato.

Ecco come si comporta quando il timer breve è di 1 ms:

2012-09-14T23:03:32.780Z Seconds: 1, counter: 869, missed 131 
2012-09-14T23:03:33.780Z Seconds: 2, counter: 1803, missed 197 
2012-09-14T23:03:34.781Z Seconds: 3, counter: 2736, missed 264 
... 
2012-09-14T23:03:41.783Z Seconds: 10, counter: 9267, missed 733 

chiamate di funzione Molti sono saltati. Qui è per 10 ms:

2012-09-14T23:01:56.363Z Seconds: 1, counter: 93, missed 7 
2012-09-14T23:01:57.363Z Seconds: 2, counter: 192, missed 8 
2012-09-14T23:01:58.364Z Seconds: 3, counter: 291, missed 9 
... 
2012-09-14T23:02:05.364Z Seconds: 10, counter: 986, missed 14 

Migliore, ma circa una chiamata di funzione viene saltata ogni secondo. E per 20 ms:

2012-09-14T23:07:18.713Z Seconds: 1, counter: 46, missed 4 
2012-09-14T23:07:19.713Z Seconds: 2, counter: 96, missed 4 
2012-09-14T23:07:20.712Z Seconds: 3, counter: 146, missed 4 
... 
2012-09-14T23:07:27.714Z Seconds: 10, counter: 495, missed 5 

Infine per 100 ms:

2012-09-14T23:04:25.804Z Seconds: 1, counter: 9, missed 1 
2012-09-14T23:04:26.803Z Seconds: 2, counter: 19, missed 1 
2012-09-14T23:04:27.804Z Seconds: 3, counter: 29, missed 1 
... 
2012-09-14T23:04:34.805Z Seconds: 10, counter: 99, missed 1 

In questo caso salta pochissimi chiamate (gap aumentata a 2 dopo 33 secondi e 3 dopo 108 secondi

.

I numeri variano, ma sono sorprendentemente coerenti tra le esecuzioni: l'esecuzione del primo benchmark 1 ms tre volte ha prodotto un ritardo dopo 10 secondi di 9267, 9259 e 9253.

Non ho trovato riferimenti per questo particolare problema. C'è questo much cited Ressig post e molte domande relative a JavaScript, ma la maggior parte suppone che il codice venga eseguito in un browser e non in node.js.

Ora per la domanda temuta: cosa sta succedendo qui? Solo scherzando; ovviamente le chiamate di funzione vengono saltate. Ma non riesco a vedere lo schema. Ho pensato che i cicli lunghi avrebbero potuto prevenire quelli brevi, ma non ha senso nel caso 1 ms. Le chiamate di funzione a ciclo breve non si sovrappongono poiché aggiornano solo una variabile e il processo node.js è vicino al 5% della CPU anche con un ciclo breve di 1 ms. Il carico medio è alto, tuttavia, a circa 0,50. Non so perché un migliaio di chiamate sottolineano così tanto il mio sistema, dal momento che node.js gestisce many more clients perfectly; deve essere vero che setInterval() is CPU intensive (o sto facendo qualcosa di sbagliato).

Una soluzione ovvia è raggruppare le chiamate di funzione utilizzando i timer più lunghi, quindi eseguire più volte chiamate di funzione a ciclo breve per simulare un timer più breve. Quindi usa il ciclo lungo come "scopa-vagone" che fa perdere le chiamate agli intervalli più bassi. Un esempio: imposta chiamate da 20 ms e 1000 ms setInterval(). Per chiamate da 1 ms: chiamarle 20 volte nel callback di 20 ms. Per la chiamata 1000 ms: controlla quante volte è stata chiamata la funzione 20ms (ad es. 47), fai tutte le chiamate rimanenti (ad esempio 3). Ma questo schema sarà un po 'complesso, dal momento che le chiamate potrebbero sovrapporsi in modi interessanti; inoltre non sarà regolare anche se potrebbe sembrare.

La vera domanda è: può essere fatto meglio, con setInterval() o altri timer all'interno di node.js? Grazie in anticipo.

risposta

10

Le funzioni di SetInterval in javascript non sono accurate. Dovresti provare ad usare un timer ad alta risoluzione. Building accurate Timers in javascript

+0

Come? Quali timer di risoluzione, una libreria? – alexfernandez

+0

Ci sono molti script timer ad alta risoluzione in google.http: //www.sitepoint.it/creating-accurate-timers-in-javascript/ – zer02

+0

Non risolverà il problema attuale. –

8

Guardate questo documento: http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg

E 'importante notare che il callback non sarà probabilmente chiamato a ritardare esattamente millisecondi - Node.js non garantisce circa la tempistica esatta di quando il callback sparerà , né delle cose che ordinano verranno attivate. La richiamata verrà chiamata il più vicino possibile all'orario specificato.

Ciò accade perché il codice dell'applicazione blocca il ciclo degli eventi. Tutti i timer e gli eventi I/O possono essere gestiti solo su nextTick.

Si può vedere questo comportamento con questo codice:

setInterval(function() { 
    console.log(Date.now()); 
    for (var i = 0; i < 100000000; i++) { 
    } 
}, 1); 

cercare di cambiare contare le iterazioni e vedere i risultati.

Idealmente, il timer verrà attivato esattamente se il tick delle applicazioni durerà meno di un ms. Ma questo non è praticabile in una vera applicazione.

+0

Avevo letto quella referenza, ma non spiega perché. Nel mio benchmark non ho codice dell'applicazione che blocca il loop degli eventi. Il riferimento a nextTick è interessante, grazie. Tuttavia, spinge il problema solo a un livello di distanza: con quale frequenza process.nextTick() attiva? Perché, e può essere cambiato? – alexfernandez

+0

No, non intendevo 'process.nextTick()' come soluzione. Volevo dire che non c'è modo di gestire i timer e gli eventi I/O più spesso del tempo di esecuzione di un'iterazione del ciclo di eventi. –

+0

Capito. Ma quanto dura una singola iterazione del ciclo degli eventi, in assenza di carico? – alexfernandez

2

La risposta sembra essere una combinazione di quelli forniti da Vadim e zer02, quindi sto lasciando un articolo qui. Come ha detto Vadim, il sistema non è in grado di gestire aggiornamenti troppo frequenti e l'aggiunta di carico al sistema non aiuta. O piuttosto il runtime non può farcela; il sistema dovrebbe essere più che capace di attivare la callback ogni millisecondo se necessario, ma per qualche ragione inspiegabile spesso non lo desidera.

La soluzione è utilizzare accurate timers, come commentato zer02. Non essere fuorviato dal nome; il meccanismo utilizzato è lo stesso setTimeout(), ma il ritardo viene regolato in base al tempo rimanente fino a quando il timer non scatta. Quindi, se il tempo è scaduto, il "timer accurato" chiamerà setTimeout (callback, 0) che viene eseguito immediatamente. Il carico di sistema è sorprendentemente inferiore a setInterval(): circa il 2% della CPU invece del 5%, nel mio campione molto poco scientifico.

Questa semplice funzione può tornare utile:

/** 
* A high resolution timer. 
*/ 
function timer(delay, callback) 
{ 
    // self-reference 
    var self = this; 

    // attributes 
    var counter = 0; 
    var start = new Date().getTime(); 

    /** 
    * Delayed running of the callback. 
    */ 
    function delayed() 
    { 
     callback(delay); 
     counter ++; 
     var diff = (new Date().getTime() - start) - counter * delay; 
     setTimeout(delayed, delay - diff); 
    } 

    // start timer 
    delayed(); 
    setTimeout(delayed, delay); 
} 

da usare, basta chiamare new timer(delay, callback);. (Sì, ho invertito l'ordine dei parametri poiché avere la callback prima è molto fastidioso.)

Un'ultima nota: setTimeout (callback, delay) non utilizza la ricorsione come temevo (come in: attendere un po 'di tempo, quindi richiama la richiamata), posiziona semplicemente il callback in una coda che verrà chiamata dal runtime al suo turno, nel contesto globale.

Problemi correlati