32

setTimeoutが node.js でどのように実装されているかを誰かが知っているかどうか疑問に思っていました。これは V8 の一部ではないことをどこかで読んだことがあると思います。私はすぐに実装を見つけようとしましたが、ソース (BIG) でそれを見つけることができませんでした。たとえば、このtimers.jsファイルを見つけました。これは、たとえばtimer_wrap.ccにリンクしています。しかし、これらのファイルは私のすべての質問に完全に答えているわけではありません。

  • V8 にはsetTimeout実装がありますか? ソースからも答えはノーだと思います。
  • どのようにsetTimeout実装されていますか?javascriptまたはネイティブ、または両方の組み合わせ? timers.js から、両方の線に沿って何かを想定します。

    var Timer = process.binding('timer_wrap').Timer;`
    
  • 複数のタイマー(setTimeout)を追加する場合、node.jsは最初に実行するものをどのように認識しますか? すべてのタイマーをコレクション (ソート済み) に追加しますか? ソートされている場合、実行する必要があるタイムアウトを見つけるのは、O(1) と O(log n) の挿入ですか? しかし、再び timers.js で、リンクリストを使用していることがわかりますか?

  • しかし、再び多くのタイマーを追加することはまったく問題ではありませんか?
  • このスクリプトを実行すると:

    var x = new Array(1000),
        len = x.length;
    
    /**
     * Returns a random integer between min and max
     * Using Math.round() will give you a non-uniform distribution!
     */
    function getRandomInt (min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
    var y = 0;
    
    for (var i = 0; i < len; i++) {
        var randomTimeout = getRandomInt(1000, 10000);
    
        console.log(i + ', ' + randomTimeout + ', ' + ++y);
        setTimeout(function () {
            console.log(arguments);
        }, randomTimeout, randomTimeout, y);
    }
    

    CPU 使用率は少し高くなりますが、それほど多くはありませんか?

  • パフォーマンスが向上する場合、ソートされたリストにこれらすべてのコールバックを 1 つずつ実装するかどうか疑問に思っています。
4

2 に答える 2

26

ほとんどの作業はすでに完了しています。setTimeoutECMAScript の一部ではないため、V8 は の実装を提供しません。Timeout使用する関数は、C クラスのラッパーであるオブジェクトのインスタンスを作成する timers.js に実装されています。

ソースには、タイマーの管理方法を説明するコメントがあります。

// Because often many sockets will have the same idle timeout we will not
// use one timeout watcher per item. It is too much overhead.  Instead
// we'll use a single watcher for all sockets with the same timeout value
// and a linked list. This technique is described in the libev manual:
// http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts

これは、リンクされた記事の #4 である二重リンク リストを使用していることを示しています。

リクエストが 1 つではなく、何千 (何百万...) もあり、すべてが同じタイムアウト値で何らかのタイムアウトを使用している場合は、さらにうまくいく可能性があります。

タイムアウトを開始するときは、タイムアウト値を計算し、タイムアウトをリストの最後に置きます。

次に、リストの先頭にあるタイムアウトが発生すると予想されるときに、ev_timer を使用して起動します (たとえば、手法 #3 を使用)。

何らかのアクティビティがある場合は、リストからタイマーを削除し、タイムアウトを再計算して、リストの最後にもう一度追加し、リストの最初から取られた場合は ev_timer を更新してください。

このようにして、タイマーの開始、停止、および更新のために O(1) 時間で無制限の数のタイムアウトを管理できますが、大きな複雑さを犠牲にして、一定のタイムアウトを使用する必要があります。一定のタイムアウトにより、リストがソートされたままになります。

Node.js は非同期操作を中心に設計されておりsetTimeout、その重要な部分です。彼らが提供するものを使用するだけです。特定のケースでそれがボトルネックであることを証明するまで、それが十分に速いと信じてください。時期尚早の最適化にとらわれないでください。

アップデート

何が起こるかというと、基本的にトップレベルでタイムアウトの辞書を持っているので、すべての 100 ミリ秒のタイムアウトがグループ化されます。新しいタイムアウトが追加されるか、最も古いタイムアウトがトリガーされるたびに、それがリストに追加されます。これは、最も古いタイムアウト、つまり最も早くトリガーされるタイムアウトがリストの先頭にあることを意味します。このリストには 1 つのタイマーがあり、リストの最初の項目が期限切れになるまでの時間に基づいて設定されます。

setTimeout同じタイムアウト値でそれぞれ 1000 回呼び出した場合、それらは呼び出した順序でリストに追加され、setTimeout並べ替えは必要ありません。非常に効率的な設定です。

于 2012-11-28T23:52:45.320 に答える
7

タイマーが多くても問題なし!UV ループがポーリングを呼び出すと、タイムアウト引数がすべてのタイマーの中で最も近いタイマーに渡されます。

[すべてのタイマーの中で最も近いタイマー]
https://github.com/joyent/node/blob/master/deps/uv/src/unix/timer.c #120

RB_MIN(uv__timers, &loop->timer_handles)  

[poll api にタイムアウト引数を渡す]
https://github.com/joyent/node/blob/master/deps/uv/src/unix/core.c #276

timeout = 0;  
if ((mode & UV_RUN_NOWAIT) == 0)  
    timeout = uv_backend_timeout(loop);  

uv__io_poll(loop, timeout); 

注: Windows OS では、ほぼ同じロジックです。

于 2013-11-20T04:23:49.567 に答える