From 583a27ce13d67cc33ded52f46fe1665c64a8fec7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 10 Feb 2021 12:43:21 +0100 Subject: [PATCH] Add benchmark script (#104) Also improve benchmarking --- .eslintrc.js | 17 ++- benchmarks/bench.js | 57 ++++++--- benchmarks/myBench.js | 121 ++++++++++++------ benchmarks/{yourWorker.js => threadWorker.js} | 2 +- ...ternalWorker.js => workerThreadsWorker.js} | 0 benchmarks/workerpoolWorker.js | 16 +++ examples/dynamicExample.js | 18 ++- examples/multiFunctionExample.js | 6 +- examples/staticExample.js | 16 ++- package-lock.json | 30 ++++- package.json | 6 +- src/workers.ts | 1 + 12 files changed, 207 insertions(+), 83 deletions(-) rename benchmarks/{yourWorker.js => threadWorker.js} (84%) rename benchmarks/{externalWorker.js => workerThreadsWorker.js} (100%) create mode 100644 benchmarks/workerpoolWorker.js diff --git a/.eslintrc.js b/.eslintrc.js index 6c710303..04c5b05b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,7 +9,7 @@ module.exports = { ecmaVersion: 2020, sourceType: 'module' }, - plugins: ['@typescript-eslint', 'prettierx'], + plugins: ['@typescript-eslint', 'promise', 'prettierx'], extends: [ 'standard', 'eslint:recommended', @@ -17,11 +17,9 @@ module.exports = { 'plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript', + 'plugin:promise/recommended', 'plugin:prettierx/standardx', - 'plugin:prettierx/@typescript-eslint', - 'prettier', - 'prettier/standard', - 'prettier/@typescript-eslint' + 'plugin:prettierx/@typescript-eslint' ], rules: { 'no-void': 'off', @@ -45,16 +43,23 @@ module.exports = { overrides: [ { files: ['*.js'], + extends: 'plugin:node/recommended', rules: { '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-var-requires': 'off' } }, { - files: ['examples/typescript/*.ts'], + files: ['examples/typescript/**/*.ts'], rules: { 'import/no-unresolved': 'off' } + }, + { + files: ['examples/**/*.js'], + rules: { + 'node/no-missing-require': 'off' + } } ] } diff --git a/benchmarks/bench.js b/benchmarks/bench.js index e0daca2d..017ac20b 100644 --- a/benchmarks/bench.js +++ b/benchmarks/bench.js @@ -1,18 +1,23 @@ const Benchmark = require('benchmark') const suite = new Benchmark.Suite() -const FixedThreadPool = require('../lib/fixed') -const DynamicThreadPool = require('../lib/dynamic') +const { FixedThreadPool } = require('../lib/index') +const { DynamicThreadPool } = require('../lib/index') const size = 30 const tasks = 1 +const LIST_FORMATTER = new Intl.ListFormat('en-US', { + style: 'long', + type: 'conjunction' +}) + // pools -const fixedPool = new FixedThreadPool(size, './yourWorker.js', { +const fixedPool = new FixedThreadPool(size, './threadWorker.js', { maxTasks: 10000 }) const dynamicPool = new DynamicThreadPool( size / 2, size * 3, - './yourWorker.js', + './threadWorker.js', { maxTasks: 10000 } ) const workerData = { proof: 'ok' } @@ -27,12 +32,18 @@ async function fixedTest () { return new Promise((resolve, reject) => { let executions = 0 for (let i = 0; i <= tasks; i++) { - fixedPool.execute(workerData).then(res => { - executions++ - if (executions === tasks) { - resolve('FINISH') - } - }) + fixedPool + .execute(workerData) + .then(res => { + executions++ + if (executions === tasks) { + return resolve('FINISH') + } + return null + }) + .catch(err => { + console.error(err) + }) } }) } @@ -41,12 +52,16 @@ async function dynamicTest () { return new Promise((resolve, reject) => { let executions = 0 for (let i = 0; i <= tasks; i++) { - dynamicPool.execute(workerData).then(res => { - executions++ - if (executions === tasks) { - resolve('FINISH') - } - }) + dynamicPool + .execute(workerData) + .then(res => { + executions++ + if (executions === tasks) { + return resolve('FINISH') + } + return null + }) + .catch(err => console.error(err)) } }) } @@ -62,11 +77,15 @@ async function test () { }) // add listeners .on('cycle', function (event) { - console.log(String(event.target)) + console.log(event.target.toString()) }) .on('complete', function () { - this.filter('fastest').map('name') - console.log('Fastest is ' + this.filter('fastest').map('name')) + console.log( + 'Fastest is ' + + LIST_FORMATTER.format(this.filter('fastest').map('name')) + ) + // eslint-disable-next-line no-process-exit + process.exit() }) // run async .run({ async: true }) diff --git a/benchmarks/myBench.js b/benchmarks/myBench.js index b714ab9e..216b338e 100644 --- a/benchmarks/myBench.js +++ b/benchmarks/myBench.js @@ -1,18 +1,24 @@ -const FixedThreadPool = require('../lib/fixed') -const DynamicThreadPool = require('../lib/dynamic') -const Pool = require('worker-threads-pool') +const { FixedThreadPool } = require('../lib/index') +const { DynamicThreadPool } = require('../lib/index') +const WorkerThreadsPool = require('worker-threads-pool') +const workerpool = require('workerpool') const tasks = 1000 const size = 16 // pools -const externalPool = new Pool({ max: size }) -const fixedPool = new FixedThreadPool(size, './yourWorker.js', { +const workerThreadsPool = new WorkerThreadsPool({ max: size }) +const workerPool = workerpool.pool('./workerpoolWorker.js', { + minWorkers: size / 2, + maxWorkers: size * 3, + workerType: 'thread' +}) +const fixedPool = new FixedThreadPool(size, './threadWorker.js', { maxTasks: 10000 }) const dynamicPool = new DynamicThreadPool( size / 2, size * 3, - './yourWorker.js', + './threadWorker.js', { maxTasks: 10000 } ) @@ -24,14 +30,20 @@ async function fixedTest () { let executions = 0 const time = Date.now() for (let i = 0; i <= tasks; i++) { - fixedPool.execute(workerData).then(res => { - executions++ - if (executions === tasks) { - console.log( - `Fixed pool take ${Date.now() - time} to work on ${executions} tasks` - ) - } - }) + fixedPool + .execute(workerData) + .then(res => { + executions++ + if (executions === tasks) { + return console.log( + `Fixed pool take ${ + Date.now() - time + }ms to work on ${executions} tasks` + ) + } + return null + }) + .catch(err => console.error(err)) } } @@ -39,26 +51,30 @@ async function dynamicTest () { let executions = 0 const time = Date.now() for (let i = 0; i <= tasks; i++) { - dynamicPool.execute(workerData).then(res => { - executions++ - if (executions === tasks) { - console.log( - `Dynamic pool take ${ - Date.now() - time - } to work on ${executions} tasks` - ) - } - }) + dynamicPool + .execute(workerData) + .then(res => { + executions++ + if (executions === tasks) { + return console.log( + `Dynamic pool take ${ + Date.now() - time + }ms to work on ${executions} tasks` + ) + } + return null + }) + .catch(err => console.error(err)) } } -async function externalPoolTest () { +async function workerThreadsPoolTest () { let executions = 0 const time = Date.now() for (let i = 0; i <= tasks; i++) { new Promise((resolve, reject) => { - externalPool.acquire( - './externalWorker.js', + workerThreadsPool.acquire( + './workerThreadsWorker.js', { workerData: workerData }, (err, worker) => { if (err) { @@ -71,22 +87,51 @@ async function externalPoolTest () { }) } ) - }).then(res => { - if (tasks === executions) { - console.log( - `External pool take ${ - Date.now() - time - } to work on ${executions} tasks` - ) - } }) + .then(res => { + if (tasks === executions) { + return console.log( + `worker threads pool take ${ + Date.now() - time + }ms to work on ${executions} tasks` + ) + } + return null + }) + .catch(err => console.error(err)) + } +} + +async function workerpoolTest () { + let executions = 0 + const time = Date.now() + for (let i = 0; i <= tasks; i++) { + workerPool + .exec('yourFunction', [workerData]) + .then(res => { + executions++ + return null + }) + .catch(err => console.error(err)) + .then(res => { + if (tasks === executions) { + return console.log( + `workerpool take ${ + Date.now() - time + }ms to work on ${executions} tasks` + ) + } + return null + }) + .catch(err => console.error(err)) } } async function test () { - fixedTest() - dynamicTest() - externalPoolTest() + await fixedTest() + await dynamicTest() + await workerThreadsPoolTest() + await workerpoolTest() } test() diff --git a/benchmarks/yourWorker.js b/benchmarks/threadWorker.js similarity index 84% rename from benchmarks/yourWorker.js rename to benchmarks/threadWorker.js index ac3959d1..b8f974d8 100644 --- a/benchmarks/yourWorker.js +++ b/benchmarks/threadWorker.js @@ -1,5 +1,5 @@ 'use strict' -const { ThreadWorker } = require('../lib/workers') +const { ThreadWorker } = require('../lib/index') function yourFunction (data) { for (let i = 0; i <= 1000; i++) { diff --git a/benchmarks/externalWorker.js b/benchmarks/workerThreadsWorker.js similarity index 100% rename from benchmarks/externalWorker.js rename to benchmarks/workerThreadsWorker.js diff --git a/benchmarks/workerpoolWorker.js b/benchmarks/workerpoolWorker.js new file mode 100644 index 00000000..c5302573 --- /dev/null +++ b/benchmarks/workerpoolWorker.js @@ -0,0 +1,16 @@ +const workerpool = require('workerpool') + +function yourFunction (data) { + for (let i = 0; i <= 1000; i++) { + const o = { + a: i + } + JSON.stringify(o) + } + // console.log('This is the main thread ' + isMainThread) + return { ok: 1 } +} + +workerpool.worker({ + yourFunction: yourFunction +}) diff --git a/examples/dynamicExample.js b/examples/dynamicExample.js index 22b4f19c..4003c534 100644 --- a/examples/dynamicExample.js +++ b/examples/dynamicExample.js @@ -10,11 +10,15 @@ pool.emitter.on('FullPool', () => maxReached++) const start = Date.now() const iterations = 1000 for (let i = 0; i <= iterations; i++) { - pool.execute({}).then(res => { - resolved++ - if (resolved === iterations) { - console.log('Time take is ' + (Date.now() - start)) - console.log('The pool was full for ' + maxReached + ' times') - } - }) + pool + .execute({}) + .then(res => { + resolved++ + if (resolved === iterations) { + console.log('Time take is ' + (Date.now() - start)) + return console.log('The pool was full for ' + maxReached + ' times') + } + return null + }) + .catch(err => console.error(err)) } diff --git a/examples/multiFunctionExample.js b/examples/multiFunctionExample.js index d1314aa9..0c4b749e 100644 --- a/examples/multiFunctionExample.js +++ b/examples/multiFunctionExample.js @@ -4,9 +4,13 @@ const pool = new FixedThreadPool(15, './multifunctionWorker.js', { onlineHandler: () => console.log('worker is online') }) -pool.execute({ fname: 'fn0', input: 'hello' }).then(res => console.log(res)) +pool + .execute({ fname: 'fn0', input: 'hello' }) + .then(res => console.log(res)) + .catch(err => console.error(err)) pool .execute({ fname: 'fn1', input: 'multifunction' }) .then(res => console.log(res)) + .catch(err => console.error(err)) setTimeout(pool.destroy.bind(pool), 3000) diff --git a/examples/staticExample.js b/examples/staticExample.js index f125c477..d81e3484 100644 --- a/examples/staticExample.js +++ b/examples/staticExample.js @@ -8,10 +8,14 @@ const pool = new FixedThreadPool(15, './yourWorker.js', { const start = Date.now() const iterations = 1000 for (let i = 0; i <= iterations; i++) { - pool.execute({}).then(res => { - resolved++ - if (resolved === iterations) { - console.log('Time take is ' + (Date.now() - start)) - } - }) + pool + .execute({}) + .then(res => { + resolved++ + if (resolved === iterations) { + return console.log('Time take is ' + (Date.now() - start)) + } + return null + }) + .catch(err => console.error(err)) } diff --git a/package-lock.json b/package-lock.json index 0e41df5b..30342d12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -665,6 +665,15 @@ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, + "after-all": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/after-all/-/after-all-2.0.2.tgz", + "integrity": "sha1-IDACmO1glLTIXJjnyK1NymKPn3M=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -3084,6 +3093,12 @@ "requires": { "has-flag": "^4.0.0" } + }, + "workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "dev": true } } }, @@ -4761,10 +4776,19 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "worker-threads-pool": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-threads-pool/-/worker-threads-pool-2.0.0.tgz", + "integrity": "sha512-5dtGbEucee6o5/kQgpyKIUoHGWf8488DP3ihZDJzDIVvH4V+NA6HdBl/I5ckI4yN1NwM68pdZDbrwac1M95mEA==", + "dev": true, + "requires": { + "after-all": "^2.0.2" + } + }, "workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index 02be7c85..b7bedf13 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "npm run build:clean && tsc", "build:clean": "rimraf lib", + "benchmark": "npm run build && node benchmarks/bench.js", "test": "npm run build && nyc mocha --exit --timeout 20000 tests/**/*.test.js", "test:debug": "mocha --inspect-brk --exit tests/**/*.test.js", "coverage": "nyc report --reporter=text-lcov | coveralls", @@ -52,7 +53,6 @@ "benchmark": "^2.1.4", "coveralls": "^3.1.0", "eslint": "^7.19.0", - "eslint-config-prettier": "^7.2.0", "eslint-config-standard": "^16.0.2", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", @@ -66,7 +66,9 @@ "prettier-plugin-organize-imports": "^1.1.1", "prettierx": "^0.17.0", "rimraf": "^3.0.2", - "typescript": "^4.1.3" + "typescript": "^4.1.4", + "worker-threads-pool": "^2.0.0", + "workerpool": "^6.1.0" }, "engines": { "node": ">=12.11.0", diff --git a/src/workers.ts b/src/workers.ts index 57f2fd95..dc4f3194 100644 --- a/src/workers.ts +++ b/src/workers.ts @@ -108,6 +108,7 @@ export class ThreadWorker extends AsyncResource { .then(res => { this.parent?.postMessage({ data: res, id: value.id }) this.lastTask = Date.now() + return null }) .catch(e => { this.parent?.postMessage({ error: e, id: value.id }) -- 2.34.1