11

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

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

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

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内の他のタイマーのいずれかを使用して、より適切に実行できるかどうかです。前もって感謝します。

4

4 に答える 4

12

javascriptのSetInterval関数は正確ではありません。高解像度のタイマーを使用するようにしてください。javascriptで正確なタイマーを構築する

于 2012-09-15T00:19:55.617 に答える
8

このドキュメントを見てください: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ミリ秒未満続く場合、タイマーが正確にトリガーされます。しかし、これは実際のアプリケーションでは実用的ではありません。

于 2012-09-14T23:59:30.353 に答える
2

答えはたまたま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)は、私が恐れていたように再帰を使用しません(たとえば、しばらく待ってからコールバックを呼び出す)。コールバックをキューに入れ、ランタイムが順番に呼び出すようにします。グローバルな文脈で来る。

于 2012-09-17T21:29:57.997 に答える
0

デバッガーを無効にして再試行しました。問題なく動作しました。

于 2020-12-21T14:34:49.467 に答える