setImmediateの実装:メッセージ、突然変異、約束、どれが速いですか?



こんにちは、 %username% ! 「処理のための関数/メソッドをキューに入れる最良の方法は何か」というトピックに関する小さな研究。その結果、比較テスト、およびsetImmediate似た関数の最終実装。 このメソッドは、スクリプトの実行を中断してブラウザを「ハングアップ」させないようにする場合に必要です。これは、巨大な初期化スクリプト、大きなデータ配列の解析、WebWorkersに頼らずに複雑な構造を構築する場合に便利です。

理解するために: setImmediatewindowオブジェクトのメソッドであり、それに渡される関数を非同期に呼び出す必要がありwindow setImmediate setTimeout(fn, 0)一種です。0は実際には0で、少なくとも4ではありません。nodejsプログラマーにとっては、 process.nextTickです。 なぜなら メソッド自体(setImmediate)には、エラーと追加のパラメーターを含む明確な標準があります。 転送された関数/メソッドをできるだけ早く非同期に実行するという抽象タスクを検討します

研究は、ブラウザスクリプトのフレームワーク内でのみ行われます。主なものは、 労働者では、そのような断片化が必要な理由は完全には明らかではありませんが、必要であれば、約束とメッセージを試すことができます。

では、postMessage、MutationObserver、Promiseのどれが最適かを調べましょう。

リサーチ


食料品版のソフトウェアでは回避することが強く推奨されているため、リストに突然変異( MutationObserver )が存在することに驚くかもしれません。 今後の見通し:彼らは嘘をつきます。 setTimeoutpostMessagePromiseMutationObserver 4つのメソッドを学習します。

setTimeout


まず、実装のnodejsバージョンに敬意を表してnextTickリサーチメソッドに名前を付けます。元のsetImmediateと混同しないようにするためです。 調査の一環として、エラー処理と追加パラメーターの分析を地獄に投げます。 だから、 nextTickを実装する最も簡単で簡潔な方法はnextTickですか? はい、同じsetTimeout(fn, 0)ます。 ( ここで行ったようにprocess.nextTickについての無知から、またはノードの古いバージョンから)

この研究でメソッドを定式化します。

  var nextTick, nextTickTO; nextTickTO = function() { var call //    , queue //  , i //      , fire //      , nextTick //        ; i = 0; queue = new Array(16); //     fire = false; call = function() { //   ? var len, s, track; //  ,    track = queue; //   len = i; // ..   queue.length   16 s = 0; //   queue = new Array(16); //     i = 0; //   fire = false; while (s < len) { track[s++](); // :       } }; nextTick = function(fn) { queue[i++] = fn; if (!fire) { fire = true; setTimeout(call, 0); } }; return nextTick; }; nextTick = nextTickTO(); 

小さなクロージャーを作成しました:メソッドの既製の配列にメモリを割り当てます(異なるエンジンの配列にメモリを割り当てる速度が研究に影響を与えないように特定のサイズのqueueすぐに割り当てます)、ポインタ( i 、同時に配列の長さのインジケータ)、渡すメソッドをcall配列に応じて、非同期開始メソッドが呼び出されることを示すインジケータ( fire )とnextTickキューnextTickが返されます。
重要なポイント:私たちが信じているテストの一部として、エラー処理、チェックはありません。 次に、このテンプレートに依存して、他の非同期技術に基づいた実装を作成します。

postMessageの方法


次に、 postMessageここまたはたとえばここ )でメソッドを見つけることができます。はるかに高速で落ち着くことができますが、実際に非常に高速なnextTickが必要な場合はすべてが変わり、モバイルデバイス市場の成長を考慮して、最適化の必要性は膨大です。
テストブロックを作成してみましょう。
  nextTickPM = function() { var fire, i, nextTick, queue; i = 0; queue = new Array(16); fire = false; window.onmessage = function(message) { //  call var data, len, s, track; data = message.data; if (data === 'a') { //  - ?  ? track = queue; len = i; s = 0; queue = new Array(16); i = 0; fire = false; while (s < len) { track[s++](); } } }; nextTick = function(fn) { queue[i++] = fn; if (!fire) { fire = true; postMessage('a', '*'); } }; return nextTick; }; 


postMessage悪いのはなぜですか? 巨大なメッセージ転送システム、ドメイン名のチェック、送信された値のチェック、および個別のメッセージキューへの配置に影響します。 残りはビーバーです。

約束の方法


さらに、私の同僚はプロミス( Promises )を使用する道を歩み、それが可能であり、より高速であると判断し、彼は正しかった。
コード:
  nextTickPR = function() { var call, fire, i, nextTick, p, queue, s; if (typeof Promise === "undefined" || Promise === null) { return nextTickMO(); } i = 0; r = 0; //   call queue = new Array(16); fire = false; p = Promise.resolve(); call = function() { var len, s, track; track = queue; len = i; s = 0; queue = new Array(16); i = 0; fire = false; while (s < len) { track[s++](); } if ((r++) % 10 === 0) { //     p = Promise.resolve(); } }; nextTick = function(fn) { queue[i++] = fn; if (!fire) { fire = true; p = p.then(call); } }; return nextTick; }; 


そして、その実装(私は省略)のメソッドは、メッセージの2倍の速度であることが判明し、単一のテンプレートの下にそれをもたらし、3倍から10倍(クロム43)に高速になりました! そして、つまずきがありました:1000コールをテストするとき、古いFirefoxは再帰の長さで呪いを開始し、コールカウンター( callr )を追加し、10コールごとに新しいPromise.resolve()を強調表示する必要がありました。 おそらく別の番号で対応しますが、将来、これらの行をゴブリンに投げることができます(悪霊には既にsetImmediate標準とコールカウンターがあります)。
また、詳細は望まれていませんでした。ブラウザ、特にモバイルブラウザによるサポート(仕事のおかげで私は特に心配しています)。

MutationObserverの方法


突然変異( MutationObserver )があり、コールバックが非同期で呼び出されること、 Node要素(HTMLElementはNodeのインスタンス)がsetAttributeメソッドを持っているという推論のパスをたどりました。これはためらうことなくプログラマが直接キューイングに関連付けます。メッセージシステムのような不必要な検証システムなし。 すでにDOMに組み込まれている本格的なノードに使用することはお勧めしませんが、DOMに埋め込まずにノードをクロージャに適切に配置した場合はどうなりますか? わかったように。

コード:
  nextTickMO = function() { var a, fire, i, nextTick, observer, queue, s; i = 0; r = 0; //    queue = new Array(16); fire = false; a = document.createElement('a'); //   observer = new MutationObserver(function() { //  call var len, s, track; track = queue; len = i; s = 0; queue = new Array(16); i = 0; fire = false; while (s < len) { track[s++](); } }); observer.observe(a, { //     attributes: true, attributeFilter: ['lang'] }); nextTick = function(fn) { queue[i++] = fn; if (!fire) { fire = true; a.setAttribute('lang', (r++).toString()); } }; return nextTick; }; 


ノードが作成され(a)、1つのノードと1つのプロパティのみをリッスンするサーバー(非常に時間がかかる場合がありますが、おそらく高速ですが、わかりません)、イベントが呼び出されることを確認するために、カウンター値が既存の属性に割り当てられます(良いかどうかわかりません) (lang)属性が選択されます。これは、速度が別の属性と同じであるためです)。
そして、この方法は、メッセージよりも高速であることが判明しました。

パス比較


突然変異はブラウザーをよりよくサポートしますが、速度を比較すると、約束と突然変異は密接に結びついています:デスクトップとタブレット上のChromeの最新バージョンとタブレット上の最新のオペラは、約束が突然変異の2倍速いことを示しました。 ネクサスのネイティブブラウザー(クローム33)であるOgnelisは、モバイルとタブレットのサファリは、約束があれば 、2倍遅くなることを示しました。 最も不愉快なことは、既存のモデルや生産されたモデルには、約束がまったくない可能性があることです。 それは、ブラウザが構築されたようなものです... postMessage(setTimeoutは言うまでもありません)ははるかに遅れており、変異のない12番目のFirefoxでのみ、本当に役に立ちました。

幸いなことに、クロムは私の手に落ちました(39)。約束と突然変異を実現する速度はほぼ等しい。 オペラについては、Webkitがあれば、存在する場合には約束があることに同意することができます。 habraeffectがオペラのどのバージョンが「過渡的」であるかを明らかにすることを願っています。 また、UCブラウザは手元にありません。一般に、データはほとんどありません。「好奇心の強い」詳細が明らかになった場合は、修正します。

IE 10と11は手元にないので、ネイティブの非標準setImmediateはこの調査では単純に省略されています。

要約:


  1. クロムが39歳以下(大きいバージョン)である場合、またはオペラが15歳以下である場合は、約束があります。
  2. そうでなければ、もしあれば突然変異。
  3. 突然変異がない場合はメッセージ、メッセージがない場合はタイマーがゼロになります。


テスト(公開されたコードと若干の相違がありますが、パフォーマンスには影響しませんが、修正した場合は統計を削除する必要があります):
jsperf.com/tick

ティックの形式でのnextTick(プロトタイプ名)の最終実装(続きのバージョン):
github.com/NightMigera/tick

Source: https://habr.com/ru/post/J275023/


All Articles