web-dev-qa-db-ja.com

node.js:setInterval()は呼び出しをスキップします

Node.jsを使用した今後のプロジェクトでは、さまざまなハウスキーピングタスクを定期的に実行する必要があります。具体的には、ミリ秒ごとのタスク、20ミリ秒ごと(1秒あたり50回)のタスク、および1秒ごとのタスクがあります。そこで、setInterval()を使用することを考えましたが、面白い結果が得られました。多くの関数呼び出しがスキップされていました。

私が使用したベンチマークは次のとおりです。

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);

1秒の長いタイマーと、変数short(この場合は1ミリ秒)を使用して調整できる短いタイマーがあります。毎秒、短いサイクルで予想されるティック数と、短いカウンターが更新された実際の回数との差を出力します。

ショートタイマーが1ミリ秒の場合の動作は次のとおりです。

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

多くの関数呼び出しはスキップされます。これは10ミリ秒です:

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

より良いですが、およそ1秒ごとに1つの関数呼び出しがスキップされます。そして20ミリ秒:

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

最後に100ミリ秒:

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

この場合、スキップする呼び出しはごくわずかです(ギャップは、33秒後に2に、108秒後に3に増加しました。

数値はさまざまですが、実行間で驚くほど一貫しています。最初の1ミリ秒のベンチマークを3回実行すると、9267、9259、および9253の10秒後に遅延が発生しました。

この特定の問題についての言及は見つかりませんでした。これがあります 多く引用されているRessigの投稿 そして多くの関連するJavaScriptの質問がありますが、ほとんどの場合、コードはnode.jsではなくブラウザーで実行されると想定しています。

さて、恐ろしい質問です。ここで何が起こっているのでしょうか。冗談ですよ;明らかに、関数呼び出しはスキップされています。しかし、私はパターンを見ることができません。長いサイクルが短いサイクルを妨げているのではないかと思いましたが、1ミリ秒の場合は意味がありません。短いサイクルの関数呼び出しは、変数を更新するだけなので重複していません。node.jsプロセスは、1ミリ秒の短いサイクルでも5%近くのCPUです。ただし、負荷平均は約0.50と高くなっています。 node.jsが処理するので、なぜ1000回の呼び出しが私のシステムにそれほどストレスを与えているのかわかりません より多くのクライアントが完全に ; setInterval()はCPUを集中的に使用します (または私が何か間違ったことをしている)ことは真実でなければなりません。

明らかな解決策は、長いタイマーを使用して関数呼び出しをグループ化し、短いサイクルの関数呼び出しを何度も実行して、短いタイマーをシミュレートすることです。次に、長いサイクルを「ほうきワゴン」として使用し、低い間隔で不在着信を発信します。例:20ミリ秒と1000ミリ秒のsetInterval()呼び出しを設定します。 1ミリ秒の呼び出しの場合:20ミリ秒のコールバックで20回呼び出します。 1000ミリ秒の呼び出しの場合:20ミリ秒の関数が呼び出された回数(例:47)を確認し、残りの呼び出し(例:3)を実行します。ただし、呼び出しが興味深い方法で重複する可能性があるため、このスキームは少し複雑になります。また、見た目は変わりますが、定期的ではありません。

本当の問題は、setInterval()またはnode.js内の他のタイマーを使用して、より適切に実行できるかどうかです。前もって感謝します。

11
alexfernandez

JavascriptのSetInterval関数は正確ではありません。高解像度タイマーの使用を試みる必要があります。 javascriptで正確なタイマーを作成する

11
zer02

このドキュメントを見てください: http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg

コールバックはおそらく正確な遅延ミリ秒で呼び出されないことに注意することが重要です-Node.jsは、コールバックが起動する正確なタイミングや、起動する順序については保証しません。コールバックは次のように呼び出されます。指定された時間に可能な限り近づけます。

これは、アプリケーションコードがイベントループをブロックするために発生します。すべてのタイマーとI/Oイベントは、nextTickでのみ処理できます。

この動作は、次のコードで確認できます。

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

反復回数を変更して、結果を確認してください。

理想的には、アプリケーションのティックが1ミリ秒未満続く場合、タイマーが正確にトリガーされます。しかし、これは実際のアプリケーションでは実用的ではありません。

8
Vadim Baryshev

答えはたまたまVadimとzer02によって与えられたものの組み合わせなので、ここに記事を残しておきます。 Vadimが言ったように、システムはあまりにも頻繁な更新に対処できず、システムに負荷を追加しても効果はありません。むしろ、ランタイムは対応できません。システムは、必要に応じてミリ秒ごとにコールバックを起動できる以上の機能を備えている必要がありますが、説明のつかない理由により、多くの場合、起動したくありません。

解決策は、zer02がコメントしたように、 正確なタイマー を使用することです。名前に惑わされないでください。使用されるメカニズムは同じsetTimeout()ですが、タイマーが起動するまでの残り時間に応じて遅延が調整されます。したがって、時間が経過すると、「正確なタイマー」はすぐに実行されるsetTimeout(callback、0)を呼び出します。驚くべきことに、システム負荷はsetInterval()よりも少なくなっています。私の非常に非科学的なサンプルでは、​​CPUの5%ではなく約2%です。

この単純な関数は便利かもしれません:

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

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

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

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

使用するには、new timer(delay, callback);を呼び出すだけです。 (はい、最初にコールバックを行うのは非常に面倒なので、パラメーターの順序を逆にしました。)それを停止するには、_timer.running = false_を設定します。

最後の注意:setTimeout(callback、delay)は、私が恐れていたように再帰を使用しません(たとえば、しばらく待ってからコールバックを呼び出す)。コールバックをキューに配置するだけで、ランタイムが順番に呼び出されます。グローバルな文脈で来る。

2
alexfernandez