LoginSignup
10

More than 3 years have passed since last update.

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

Posted at

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

語弊がありますが似たようなモジュールに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で、親スレッドから子スレッドにデータを送信する例:

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

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

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

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

マルチコアを生かした分散処理をしようとすると、今や選択肢として歴史の深い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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10