Node.jsでもマルチスレッドプログラミングができるworker_threadsというモジュールがあります。
- worker_threadsの概要はこちらを参照→ Node.js: CPU負荷で3秒かかっていた処理を「Worker Threads」で1秒に時短する - Qiita
語弊がありますが似たようなモジュールに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')constworker=newWorker('./worker.js')worker.postMessage('Hello!')
child_processで、親プロセスから子プロセスにデータを送信する例:
const{fork}=require('child_process')constchildProcess=fork('./worker.js')childProcess.send('Hello!')
どちらも似たようなワーカー間通信ができるのがコードからも分かるかと思います。
ちなみに、worker_threadstとchild_processの2つは、そもそもアーキテクチャが異なるので、通信方法もデータのシリアライズ方法は異なります。そのへんの違いについては、簡単な比較表を載せておきます:
どちらのワーカー間通信のほうが速い?
マルチコアを生かした分散処理をしようとすると、今や選択肢として歴史の深いchild_processと、新機能のworker_threadsの2つの選択肢があるわけですが、ワーカー間通信の効率という観点ではどちらが優れているのでしょうか? 気になって検証しました。
検証方法
検証方法としては下記のとおりです。
- 親ワーカーと子ワーカー間で、N回メッセージの送受信を繰り返す。
- そのN回の送受信にかかる時間を測定する。
- 送受信するデータのパターンをいくつか用意し、データの内容によってどういう違いがでるかもついでに調べる。
- 各データパターンごとに1回ずつ測定。
検証コード
検証するために書いたコードが下記です。
const{repeat,data}=require('./config.js')const{fork}=require('child_process')if(process.send===undefined){// parent processletcount=0fork(__filename).on('message',function(message){if(message==='end'){console.timeEnd('test')this.kill()return}elseif(message==='start'){console.time('test')}this.send(++count<=repeat)})}else{process.send('start')process.on('message',continues=>{process.send(continues?data:'end')})}
const{repeat,data}=require('./config.js')const{isMainThread,Worker,parentPort}=require('worker_threads')if(isMainThread){letcount=0newWorker(__filename).on('message',function(message){if(message==='end'){console.timeEnd('test')this.terminate()return}elseif(message==='start'){console.time('test')}this.postMessage(++count<=repeat)})}else{parentPort.postMessage('start')parentPort.on('message',continues=>{parentPort.postMessage(continues?data:'end')})}
パラメータは共通して設定できるように別ファイルにしました:
module.exports={repeat:1000,data:true,// data: 'a',// data: Array(10000).fill('x')// data: 'a'.repeat(100000),// data: Array(10000).fill({a: 1}),}
検証結果
測定結果としては、下記のグラフのようになりました。
3つ目の1万要素ある配列を送受信するのを除くと、worker_threadsのほうがchild_processより2〜11倍速いということがわかりました。