From cb70b19deb97dc2c8ad1a769e59e870ee37f8e4d Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 18 Apr 2023 20:07:08 +0200 Subject: [PATCH] perf: alternate worker selection between start and end of worker nodes in some worker choice strategies MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- CHANGELOG.md | 4 ++ package.json | 4 +- pnpm-lock.yaml | 40 +++++++++---------- src/pools/abstract-pool.ts | 14 +++++++ src/pools/pool.ts | 12 +++++- .../abstract-worker-choice-strategy.ts | 18 +++++++++ .../less-busy-worker-choice-strategy.ts | 2 +- .../less-used-worker-choice-strategy.ts | 2 +- 8 files changed, 71 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ee9476..c09124a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Optimize free worker finding in worker choice strategies. + ## [2.4.10] - 2023-04-15 ### Fixed diff --git a/package.json b/package.json index 3b204971..6b773ef2 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "eslint-define-config": "^1.18.0", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jsdoc": "^41.1.2", + "eslint-plugin-jsdoc": "^43.0.3", "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-spellcheck": "^0.0.20", @@ -125,7 +125,7 @@ "prettier": "^2.8.7", "prettier-plugin-organize-imports": "^3.2.2", "release-it": "^15.10.1", - "rollup": "^3.20.4", + "rollup": "^3.20.6", "rollup-plugin-analyzer": "^4.0.0", "rollup-plugin-command": "^1.1.3", "rollup-plugin-delete": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eec360d8..67ffcb3f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,10 +15,10 @@ devDependencies: version: 3.1.0(release-it@15.10.1) '@rollup/plugin-terser': specifier: ^0.4.1 - version: 0.4.1(rollup@3.20.4) + version: 0.4.1(rollup@3.20.6) '@rollup/plugin-typescript': specifier: ^11.1.0 - version: 11.1.0(rollup@3.20.4)(typescript@5.0.4) + version: 11.1.0(rollup@3.20.6)(typescript@5.0.4) '@types/node': specifier: ^18.15.11 version: 18.15.11 @@ -53,8 +53,8 @@ devDependencies: specifier: ^2.27.5 version: 2.27.5(@typescript-eslint/parser@5.59.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0) eslint-plugin-jsdoc: - specifier: ^41.1.2 - version: 41.1.2(eslint@8.38.0) + specifier: ^43.0.3 + version: 43.0.3(eslint@8.38.0) eslint-plugin-n: specifier: ^15.7.0 version: 15.7.0(eslint@8.38.0) @@ -95,8 +95,8 @@ devDependencies: specifier: ^15.10.1 version: 15.10.1 rollup: - specifier: ^3.20.4 - version: 3.20.4 + specifier: ^3.20.6 + version: 3.20.6 rollup-plugin-analyzer: specifier: ^4.0.0 version: 4.0.0 @@ -719,7 +719,7 @@ packages: string-template: 1.0.0 dev: true - /@rollup/plugin-terser@0.4.1(rollup@3.20.4): + /@rollup/plugin-terser@0.4.1(rollup@3.20.6): resolution: {integrity: sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -728,13 +728,13 @@ packages: rollup: optional: true dependencies: - rollup: 3.20.4 + rollup: 3.20.6 serialize-javascript: 6.0.1 smob: 0.0.6 - terser: 5.16.9 + terser: 5.17.0 dev: true - /@rollup/plugin-typescript@11.1.0(rollup@3.20.4)(typescript@5.0.4): + /@rollup/plugin-typescript@11.1.0(rollup@3.20.6)(typescript@5.0.4): resolution: {integrity: sha512-86flrfE+bSHB69znnTV6kVjkncs2LBMhcTCyxWgRxLyfXfQrxg4UwlAqENnjrrxnSNS/XKCDJCl8EkdFJVHOxw==} engines: {node: '>=14.0.0'} peerDependencies: @@ -747,13 +747,13 @@ packages: tslib: optional: true dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.20.4) + '@rollup/pluginutils': 5.0.2(rollup@3.20.6) resolve: 1.22.2 - rollup: 3.20.4 + rollup: 3.20.6 typescript: 5.0.4 dev: true - /@rollup/pluginutils@5.0.2(rollup@3.20.4): + /@rollup/pluginutils@5.0.2(rollup@3.20.6): resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -765,7 +765,7 @@ packages: '@types/estree': 1.0.0 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 3.20.4 + rollup: 3.20.6 dev: true /@sinclair/typebox@0.25.24: @@ -2316,8 +2316,8 @@ packages: - supports-color dev: true - /eslint-plugin-jsdoc@41.1.2(eslint@8.38.0): - resolution: {integrity: sha512-MePJXdGiPW7AG06CU5GbKzYtKpoHwTq1lKijjq+RwL/cQkZtBZ59Zbv5Ep0RVxSMnq6242249/n+w4XrTZ1Afg==} + /eslint-plugin-jsdoc@43.0.3(eslint@8.38.0): + resolution: {integrity: sha512-tHlpaUqB8ih2IhQw7Es/R3Z3anQZVfPUb33nUAVOgIcMugVYyD1ZE/KXjjN8HxykZsV1IXqrKZkKpUBrEi3G9Q==} engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -5154,8 +5154,8 @@ packages: del: 5.1.0 dev: true - /rollup@3.20.4: - resolution: {integrity: sha512-n7J4tuctZXUErM9Uc916httwqmTc63zzCr2+TLCiSCpfO/Xuk3g/marGN1IlRJZi+QF3XMYx75PxXRfZDVgaRw==} + /rollup@3.20.6: + resolution: {integrity: sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -5615,8 +5615,8 @@ packages: resolution: {integrity: sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==} dev: true - /terser@5.16.9: - resolution: {integrity: sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==} + /terser@5.17.0: + resolution: {integrity: sha512-3die3+pYW4mta4xF6K8Wtf7id8+oYyfqtAhjwzqY01+CfDSDMx/VA1Sp8sXWs5AVNIoAKoUfmp/gnPqRjBxuDA==} engines: {node: '>=10'} hasBin: true dependencies: diff --git a/src/pools/abstract-pool.ts b/src/pools/abstract-pool.ts index 4c78c28f..67a22ffe 100644 --- a/src/pools/abstract-pool.ts +++ b/src/pools/abstract-pool.ts @@ -306,6 +306,20 @@ export abstract class AbstractPool< }) } + /** @inheritDoc */ + public findLastFreeWorkerNodeKey (): number { + // It requires node >= 18.0.0 + // return this.workerNodes.findLastIndex(workerNode => { + // return workerNode.tasksUsage?.running === 0 + // }) + for (let i = this.workerNodes.length - 1; i >= 0; i--) { + if (this.workerNodes[i].tasksUsage?.running === 0) { + return i + } + } + return -1 + } + /** @inheritDoc */ public async execute (data?: Data): Promise { const [workerNodeKey, workerNode] = this.chooseWorkerNode() diff --git a/src/pools/pool.ts b/src/pools/pool.ts index d540bbd2..bf2ac2c2 100644 --- a/src/pools/pool.ts +++ b/src/pools/pool.ts @@ -141,7 +141,7 @@ export interface IPool< */ readonly emitter?: PoolEmitter /** - * Finds a free worker node key based on the number of tasks the worker has applied. + * Finds the first free worker node key based on the number of tasks the worker has applied. * * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned. * @@ -150,6 +150,16 @@ export interface IPool< * @returns A worker node key if there is one, `-1` otherwise. */ findFreeWorkerNodeKey: () => number + /** + * Finds the last free worker node key based on the number of tasks the worker has applied. + * + * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned. + * + * If no free worker is found, `-1` is returned. + * + * @returns A worker node key if there is one, `-1` otherwise. + */ + findLastFreeWorkerNodeKey: () => number /** * Executes the function specified in the worker constructor with the task data input parameter. * diff --git a/src/pools/selection-strategies/abstract-worker-choice-strategy.ts b/src/pools/selection-strategies/abstract-worker-choice-strategy.ts index 2f190578..bddb8b76 100644 --- a/src/pools/selection-strategies/abstract-worker-choice-strategy.ts +++ b/src/pools/selection-strategies/abstract-worker-choice-strategy.ts @@ -19,6 +19,10 @@ export abstract class AbstractWorkerChoiceStrategy< Data = unknown, Response = unknown > implements IWorkerChoiceStrategy { + /** + * Toggles finding the last free worker node key. + */ + private toggleFindLastFreeWorkerNodeKey: boolean = false /** @inheritDoc */ protected readonly isDynamicPool: boolean /** @inheritDoc */ @@ -68,4 +72,18 @@ export abstract class AbstractWorkerChoiceStrategy< this.checkOptions(opts) this.opts = opts } + + /** + * Finds a free worker node key. + * + * @returns The free worker node key or `-1` if there is no free worker node. + */ + protected findFreeWorkerNodeKey (): number { + if (this.toggleFindLastFreeWorkerNodeKey) { + this.toggleFindLastFreeWorkerNodeKey = false + return this.pool.findLastFreeWorkerNodeKey() + } + this.toggleFindLastFreeWorkerNodeKey = true + return this.pool.findFreeWorkerNodeKey() + } } diff --git a/src/pools/selection-strategies/less-busy-worker-choice-strategy.ts b/src/pools/selection-strategies/less-busy-worker-choice-strategy.ts index d862405c..10fc3bf7 100644 --- a/src/pools/selection-strategies/less-busy-worker-choice-strategy.ts +++ b/src/pools/selection-strategies/less-busy-worker-choice-strategy.ts @@ -45,7 +45,7 @@ export class LessBusyWorkerChoiceStrategy< /** @inheritDoc */ public choose (): number { - const freeWorkerNodeKey = this.pool.findFreeWorkerNodeKey() + const freeWorkerNodeKey = this.findFreeWorkerNodeKey() if (freeWorkerNodeKey !== -1) { return freeWorkerNodeKey } diff --git a/src/pools/selection-strategies/less-used-worker-choice-strategy.ts b/src/pools/selection-strategies/less-used-worker-choice-strategy.ts index 51039c95..e3c395f4 100644 --- a/src/pools/selection-strategies/less-used-worker-choice-strategy.ts +++ b/src/pools/selection-strategies/less-used-worker-choice-strategy.ts @@ -37,7 +37,7 @@ export class LessUsedWorkerChoiceStrategy< /** @inheritDoc */ public choose (): number { - const freeWorkerNodeKey = this.pool.findFreeWorkerNodeKey() + const freeWorkerNodeKey = this.findFreeWorkerNodeKey() if (freeWorkerNodeKey !== -1) { return freeWorkerNodeKey } -- 2.34.1