Node.js: worker_threadsのスレッド間通信は、child_processのプロセス間通信の2〜11倍速い。

Node.jsでもマルチスレッドプログラミングができるworker_threadsというモジュールがあります。

* worker_threadsの概要はこちらを参照→ [Node.js: CPU負荷で3秒かかっていた処理を「Worker Threads」で1秒に時短する - Qiita](https://qiita.com/suin/items/bce351c812603d413841)

語弊がありますが似たようなモジュールにchild_processというものもあります。本稿では、worker_threadsとchild_processをワーカー間通信の速さという観点で比較していきます。

## 本稿でわかること

* child_processとworker_threadsどっちの通信速度のほうが速いか？

## child_processは古参モジュールとして、マルチコアでの分散処理を支えてきた

worker_threadsは比較的新しいモジュールで、スレッドがNode.jsに導入される以前は、マルチコア環境のリソースを活かすには、Node.jsでは複数のプロセスを起動して負荷分散するというアプローチが取られてきました。Node.jsでマルチプロセス型の分散処理をするためによく使われるのが、child_processやclusterといったモジュールです。

## worker_threadsもchild_processも似たようなワーカー間通信ができる

worker_threadsもchild_processも、処理をフォークして並列処理できるだけでなく、親ワーカーと子ワーカーの通信ができます。用語が多少異なりますが、worker_threadsの場合は、親スレッドと子スレッドとの間でデータを送受信できます。child_processの場合も、親プロセスと子プロセスの間でデータの送受信が可能です。

worker_threadsで、親スレッドから子スレッドにデータを送信する例:

```js
const {Worker} = require('worker_threads')
const worker = new Worker('./worker.js')
worker.postMessage('Hello!')
```

child_processで、親プロセスから子プロセスにデータを送信する例:

```js
const {fork} = require('child_process')
const childProcess = fork('./worker.js')
childProcess.send('Hello!')
```

どちらも似たようなワーカー間通信ができるのがコードからも分かるかと思います。

ちなみに、worker_threadstとchild_processの2つは、そもそもアーキテクチャが異なるので、通信方法もデータのシリアライズ方法は異なります。そのへんの違いについては、簡単な比較表を載せておきます:

![Pasted_Image_2020_03_12_12_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/347a3e97-a916-5e4e-8682-221797aa0064.png)

## どちらのワーカー間通信のほうが速い？

マルチコアを生かした分散処理をしようとすると、今や選択肢として歴史の深いchild_processと、新機能のworker_threadsの2つの選択肢があるわけですが、ワーカー間通信の効率という観点ではどちらが優れているのでしょうか？ 気になって検証しました。

### 検証方法

検証方法としては下記のとおりです。

* 親ワーカーと子ワーカー間で、N回メッセージの送受信を繰り返す。
* そのN回の送受信にかかる時間を測定する。
* 送受信するデータのパターンをいくつか用意し、データの内容によってどういう違いがでるかもついでに調べる。
* 各データパターンごとに1回ずつ測定。

### 検証コード

検証するために書いたコードが下記です。

```child-process.js
const {repeat, data} = require('./config.js')
const {fork} = require('child_process')
if (process.send === undefined) {
  // parent process
  let count = 0
  fork(__filename)
    .on('message', function (message) {
      if (message === 'end') {
        console.timeEnd('test')
        this.kill()
        return
      } else if (message === 'start') {
        console.time('test')
      }
      this.send(++count <= repeat)
    })
} else {
  process.send('start')
  process.on('message', continues => {
    process.send(continues ? data : 'end')
  })
}
```

```worker-threads.js
const {repeat, data} = require('./config.js')
const {isMainThread, Worker, parentPort} = require('worker_threads')
if (isMainThread) {
  let count = 0
  new Worker(__filename)
    .on('message', function (message) {
      if (message === 'end') {
        console.timeEnd('test')
        this.terminate()
        return
      } else if (message === 'start') {
        console.time('test')
      }
      this.postMessage(++count <= repeat)
    })
} else {
  parentPort.postMessage('start')
  parentPort.on('message', continues => {
    parentPort.postMessage(continues ? data : 'end')
  })
}
```

パラメータは共通して設定できるように別ファイルにしました:

```config.js
module.exports = {
  repeat: 1000,
  data: true,
  // data: 'a',
  // data: Array(10000).fill('x')
  // data: 'a'.repeat(100000),
  // data: Array(10000).fill({a: 1}),
}
```

### 検証結果

測定結果としては、下記のグラフのようになりました。

![Pasted_Image_2020_03_13_14_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889/d5f436a0-1462-0662-502e-b9272e097663.png)

3つ目の1万要素ある配列を送受信するのを除くと、worker_threadsのほうがchild_processより2〜11倍速いということがわかりました。