Flash Advent Calendar 7日目 - 俺のWeb Workersがこんなに遅いわけがない外伝 -

今日は、らくさんのこの記事の外伝となります。

まずは、本家の記事を読んでいただければと思います。

[本家「俺のWeb Workersがこんなに遅いわけがない」](https://lab.sonicmoov.com/development/javascript/web-workers-with-transferable-objects/)

# 目次

* おさらい
* worker用のJSファイルを作らない
* buffer以外のobjectやarrayの転送で遅延するケース


# おさらい

遅くなる例

```JavaScript
const typedArray = new Uint8Array(1024 * 1024 * 256);
worker.postMessage({ "data": typedArray });
```

原因は`postMessage`の第2引数にbufferをセットせずに送信した結果
複製されたデータを転送するのに異常な時間がかかる事です。

この回避策として、下記の実装のように第2引数の配列の`buffer`を設置します。
コピー無しで転送されるため大きなデータでも速度がでます。

```JavaScript
const typedArray = new Uint8Array(1024 * 1024 * 256);
worker.postMessage({ "data": typedArray }, [typedArray.buffer]);
```

## 注意点
この時にメインスレッド側のtypedArrayの中身は空っぽになります。

```JavaScript
const typedArray = new Uint8Array(1024 * 1024 * 256);
worker.postMessage({ "data": typedArray }, [typedArray.buffer]);

console.log(typedArray.length); // 0が出力
```

同じように、workerからメインスレッドに渡すとworker側のbufferは空っぽになります。

```JavaScript
this.addEventListener("message", function (event)
{ 
    // 加工処理
    const typedArray = this.edit(event.data);   

    this.postMessage({ "data": typedArray }, [typedArray.buffer]);

    console.log(typedArray.length); // 0が出力
});
```

加工する前のbufferを保管したい場合は、どこかで複製が必要になるので注意が必要です。

```JavaScript
const typedArray = new Uint8Array(1024 * 1024 * 256);
const cloneArray = typedArray.slice(0);

worker.postMessage({ "data": typedArray }, [typedArray.buffer]);

console.log(typedArray.length); // 0が出力
console.log(cloneArray.length); // 268435456が出力
```

# worker用のJSファイルを作らない

今回は`swf2js.js`だけで完結したいという目標があったので
worker用のJSファイルを設置しないで作り込みたいので以下のような対応をしました。


```JavaScript
const worker = new Worker(URL.createObjectURL(new Blob(
    ['###JavaScriptの処理###'], 
    { "type": "text/javascript" }
));
```

個別にworkerディレクトリを作ります。

```Linuc
src/
├── worker/
│   ├── UnzipWorker.js
│   ├── UnLZMAWorker.js
│   ├── SwfParserWorker.js
etc...
```

その中にあるJSファイルをgulpで強制的に`Minify`し
文字列の`###JavaScriptの処理###`をキーにして置換してしまいます。

gulp起動後

```Linuc
src/
├── worker/
│   ├── UnzipWorker.js
│   ├── UnzipWorker.min.js <= MinifyされたJSファイルが自動で上書きor追加される
│   ├── UnLZMAWorker.js
│   ├── UnLZMAWorker.min.js <= MinifyされたJSファイルが自動で上書きor追加される
│   ├── SwfParserWorker.js
│   ├── SwfParserWorker.min.js <= MinifyされたJSファイルが自動で上書きor追加される
etc...
```

gulp側の処理

```JavaScript

const fs = require("fs");

...省略

replace(
    "###JavaScriptの処理###", 
    fs.readFileSync("src/worker/UnzipWorker.min.js", "utf8")
        .replace(/\\/g, "\\\\") // バックスラッシュをエスケープ
        .replace(/'/g, "\\'") // 置換元の文字列をシングルクォーテーションでくくってるので、シングルクォーテーションをエスケープ
        .replace(/\n/g, "") // 改行を削除
);

...省略

```

これで一個のファイルに複数のworkerを設置できます。

# buffer以外のobjectやarrayの転送で遅くなるケース

大容量もしくはネストの深い`object`や`array`を転送すると
送る側も受け取る側も負荷が高くなり、そこがボトルネックで速度遅延します。

回避策
1. TypedArrayに変換できないか検討
2. できるだけ小さい単位で転送する

1は上の説明の通り、一番転送速度が安定します。
ただ、文字列など使う場合はこの方法が使えません。
なので、2の「できるだけ小さい単位で転送する」事で回避を試みます。

## worker側でメインスレッドにデータを送る

```javaScript
function 関数()
{
    const object = {};

    // objectを作り込むロジック
    ...省略

    // できるだけ小さい単位でメインスレッドに送る
    globalThis.postMessage({
        "key": "分岐しやすい文字列",
        "data": object
    });
}
```

## メインスレッド側の個別受け取り処理

```javaScript

const worker = new Worker(URL.createObjectURL(new Blob(
    ['###JavaScriptの処理###'], 
    { "type": "text/javascript" }
));

worker.onmessage = function (event)
{
    switch (event.key) {

        case "分岐しやすい文字列1":
            // 受け取り処理を実装1
            return ;

        case "分岐しやすい文字列2":
            // 受け取り処理を実装2
            return ;

        case "分岐しやすい文字列3":
            // 受け取り処理を実装3
            return ;

        case "分岐しやすい文字列4":
            // 受け取り処理を実装4
            return ;

        default:
            // workerを終了する
            event.target.terminate();
            break;

    }
}

```

workerはメインスレッドに負荷がかからない便利な機能ですが
使い方を間違えると、サブスレッドとメインスレッドの両方に負荷がかかってしまいます。

この記事が何かの役に立てば嬉しいです。

明日からはWebGLに移行した時に色々と苦戦した事を書こうと思います。