このビデオには 15+MB の BAS 弾幕スクリプトが解析される必要があります。これにより、ページが約 7 秒間フリーズし、その間 UI が凍結され、非常に悪い体験になります。Web Workers を使用して最適化を行い、解析を Web Workers に配置すると、UI スレッドのブロックによるページのフリーズを回避することができます。
シングルスレッド#
解析関数をシミュレートするために parse を使用します
index.js
function parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod'
}
console.log(parse(1000));
この時、ページは 1 秒間フリーズし、その後に 'DIYgod' という出力がされます。
Web Workers の使用#
index.js
const wk = new Worker('worker.js');
wk.postMessage(1000);
wk.addEventListener('message', (e) => {
console.log(e.data);
});
worker.js
function parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
onmessage = function (e) {
postMessage(parse(e.data));
}
これは Web Workers の基本的な使用法です。index.js は 1000 を worker.js に渡し、worker.js はバックグラウンドで 1000ms を解析し、結果の 'DIYgod' を index.js に返します。これにより、解析はもはや JavaScript のメインスレッドを占有せず、ページがフリーズするのを回避します。
インラインワーカー#
前のステップでは、2 つの js ファイル、index.js と worker.js をロードしました。HTML で index.js を参照し、index.js が worker.js をロードするようにしました。しかし、独自のワーカーファイルを作成したくない場合はどうすればよいですか?
index.js
const workerBlob = new Blob([`function parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
onmessage = function (e) {
postMessage(parse(e.data));
}`], { type: 'application/javascript' });
const workerURL = URL.createObjectURL(workerBlob);
const wk = new Worker(workerURL);
wk.postMessage(1000);
wk.addEventListener('message', (e) => {
console.log(e.data);
});
URL.createObjectURL (blob) は、blob を表す DOMString を作成します。
コンソールのネットワークタブを開くと、blob:http://example.com/16215a1e-21d4-450c-b441-070e1981b69d
のような奇妙なリンクの js ファイルがブラウザによってロードされるのを見ることができます。この js ファイルの内容は、workerBlob に渡された文字列の内容です。
この URL は一意であり、そのライフサイクルはそれを作成したウィンドウのドキュメントにバインドされます。ページが存在する限り、その URL は常に有効です。
webpack worker-loader の使用#
前のステップでは、js コードを文字列に入れましたが、これはモジュールを分割することができず、後のメンテナンスには適していません。プロジェクトが webpack を使用している場合は、worker-loaderをインストールすることでこの問題を解決できます。
index.js
import WK from 'worker-loader?inline=true&fallback=false!./worker.js';
const wk = new WK();
wk.postMessage(1000);
wk.addEventListener('message', (e) => {
console.log(e.data);
});
worker.js
import Parse from './parse.js';
self.addEventListener('message', (e) => {
self.postMessage(Parse(e.data));
});
parse.js
function Parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
export default Parse;
worker-loader を使用して worker.js モジュールを参照するだけで、残りの部分は worker-loader が自動的に処理してくれます。最終的なコンパイル結果は、前のステップのコードと似ています。
Web Workers を使用しない場合と比較すると:
index.js
import Parse from './parse.js';
console.log(Parse(1000));
parse.js(変更なし)
function Parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
export default Parse;
これにより、既存の解析モジュールを変更する必要はありません。侵入しない方法で、単に worker.js の中継モジュールを追加し、呼び出し方法を変更するだけで済みます。メンテナンスも簡単です。
パフォーマンス#
計算を 4 つのワーカーに配置すると、計算は 4 倍速くなりますか?
いいえ、それは 4 倍速くなるだけでなく、少し遅くなります。
Web Workers は計算時間を短縮するためではなく、UI スレッドのフリーズを回避するために使用されます。スレッドの作成、スレッドのスケジューリング、データの転送などの動作は、計算をわずかに遅くします。
私は、最初のビデオが異なるワーカーの数で 100 のコメントを解析する時間を記録しました。7 回の記録の平均値を取りました:
ワーカーの数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 10 |
---|---|---|---|---|---|---|---|---|
平均時間 (ms) | 6085 | 8216 | 6310 | 6388 | 6483 | 6317 | 6475 | 7233 |
ワーカーを使用しない場合、解析速度が最も速くなります。1 つのワーカーの速度は他の速度よりも明らかに遅く、2 3 4 5 6 個のワーカーの速度には明らかな違いはありませんが、ワーカーの数が増えるにつれて速度が徐々に遅くなります。
また、コメントが少ないビデオもテストしましたが、1 2 3 4 5 個のワーカーの速度はほぼ同じでした。
最終的に、2 つのワーカーを使用して解析することにしました。
最適化の結果は素晴らしく、解析が完了するのを待つ必要がなく、他の操作を行うことができます。ビデオを再生しながら解析を行うこともできますが、解析が完了するまで解析されていないコメントは表示されません。