この記事は最終更新日から1年以上が経過しています。
@programming
投稿日 2022/2/10
更新日 2022/2/10 ✏
child_process#fork()とWorkerの比較
Node.jsでスレッド処理っぽいことをする上で、child_process
モジュールのfork()
とworker_threads
モジュールのWorker
で使いどころが割と被るのですが、実際どちらが性能が良いのかを簡単に調査してみました。
目次:
環境
- Ubuntu 16.04.7 LTS
- Node.js v16.13.2
結論
先に結論からいうと、筆者のLinux環境では以下の点でWorker
に軍配が上がりました。(もちろんマシン環境等にもよると思います。)
- 両者とも処理速度は殆ど変わらない
Worker
はイベントループのブロッキングが明らかに小さい:Worker
は呼び出し元に 3 ms 以上のブロッキングは無し。- 一方、
fork
は呼び出し元に 3 ms 以上のブロッキングが発生。
以下は実証コードと実行結果です。
実証コード
fork版
main_fork.js (メイン)
// イベントループのブロッキング調査:
require('blocked-at')((time, stack) => {
console.log(`Main process blocking: ${time}ms block detected.`);
}, { threshold: 3 }); // 閾値は3ms
const { fork } = require('child_process');
(() => {
const start = process.hrtime();
// 重い処理をメインスレッドから切り離す
const child = fork(__dirname + '/getCount_fork.js');
child.on('message', (message) => {
console.log('results from forked process:', message);
const diff = process.hrtime(start);
console.log(diff[0] * 1e9 + diff[1]);
});
child.send('START');
})();
getCount_fork.js (呼び出し先)
// fork版: getCountの処理結果をメインへ返す
process.on('message', (message) => {
if (message == 'START') {
console.log('Child process received START message');
const slowResult = require('./getCount.js')();
const message = `totalCount: ${slowResult}`;
process.send(message);
}
});
Worker版
main_worker.js (メイン)
// イベントループのブロッキング調査:
require('blocked-at')((time, stack) => {
console.log(`Main process blocking: ${time}ms block detected.`);
}, { threshold: 3 }); // 閾値は3ms
const { Worker } = require('worker_threads');
(() => {
const start = process.hrtime();
// 重い処理をメインスレッドから切り離す:
const workerFile = __dirname + '/getCount_worker.js';
const worker = new Worker(workerFile);
worker.on('message', (message) => {
console.log('results from worker process:', message);
const diff = process.hrtime(start);
console.log(diff[0] * 1e9 + diff[1]);
});
worker.postMessage('START');
})();
getCount_worker.js (呼び出し先)
// worker版: getCountの処理結果をメインへ返す
const { parentPort } = require('worker_threads');
parentPort.on('message', (message) => {
if (message == 'START') {
console.log('Child process received START message');
const slowResult = require('./getCount.js')();
const message = `totalCount: ${slowResult}`;
parentPort.postMessage(message);
}
});
共通処理
getCount.js (とても遅い処理)
// とても遅い処理
module.exports = function getCount() {
let counter = 0;
while (counter < 5000000000) {
counter++;
}
return counter;
}
実行結果
fork版:
$ node main_fork.js
Child process received START message
results from forked process: totalCount: 5000000000
5651059080
Main process blocking: 3.386962999343872ms block detected.
3 ms 以上のブロッキングが発生!
次にWorker版:
$ node main_worker.js
Child process received START message
results from worker process: totalCount: 5000000000
5875327627
僅かに処理は遅い(と言っても誤差の範囲内)が、ブロッキングの発生は無し。
以上です。