perf: remove unneeded class indirection for dynamic pool in worker
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 5 Apr 2023 10:40:40 +0000 (12:40 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 5 Apr 2023 10:40:40 +0000 (12:40 +0200)
selection code

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
CHANGELOG.md
benchmarks/worker-selection/less.js [moved from benchmarks/worker-selection/lru.js with 100% similarity]
src/pools/cluster/dynamic.ts
src/pools/pool-internal.ts
src/pools/selection-strategies/abstract-worker-choice-strategy.ts
src/pools/selection-strategies/dynamic-pool-worker-choice-strategy.ts [deleted file]
src/pools/selection-strategies/selection-strategies-types.ts
src/pools/selection-strategies/worker-choice-strategy-context.ts
src/pools/thread/dynamic.ts
tests/pools/selection-strategies/selection-strategies.test.js
tests/pools/selection-strategies/worker-choice-strategy-context.test.js

index f1b026dc34431a93d0b3500b2a8ca92db59ea1f9..bc047e439b2a857f80b30ded2260f21fb14add81 100644 (file)
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Changed
+
+- Optimize worker choice strategy for dynamic pool.
+
 ### Fixed
 
 - Ensure dynamic pool does not alter worker choice strategy expected behavior.
index c4fc788cd8dcdd0528bca2234caead2d9fc70ccd..79f684cbd39a2990f0b7f968e41cf7dda9d70569 100644 (file)
@@ -6,7 +6,7 @@ import { FixedClusterPool } from './fixed'
  * A cluster pool with a dynamic number of workers, but a guaranteed minimum number of workers.
  *
  * This cluster pool creates new workers when the others are busy, up to the maximum number of workers.
- * When the maximum number of workers is reached, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
+ * When the maximum number of workers is reached and workers are busy, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
  *
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
  * @typeParam Response - Type of response of execution. This can only be serializable data.
index 806107ec74ea29402408abaf3cf95a8f0325581c..2d92f2f0c48dac3c25a190aefa5485cde5f8715e 100644 (file)
@@ -34,8 +34,8 @@ export interface WorkerType<Worker extends IPoolWorker> {
  * Internal contract definition for a poolifier pool.
  *
  * @typeParam Worker - Type of worker which manages this pool.
- * @typeParam Data - Type of data sent to the worker.
- * @typeParam Response - Type of response of execution.
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
  */
 export interface IPoolInternal<
   Worker extends IPoolWorker,
index 3a5d6e04a3ddd46e0e78e8d8a57062e8e9ba966c..aa370fe433e74065e4155d8612499abef5ec59fb 100644 (file)
@@ -7,7 +7,7 @@ import type {
 } from './selection-strategies-types'
 
 /**
- * Abstract worker choice strategy class.
+ * Worker choice strategy abstract base class.
  *
  * @typeParam Worker - Type of worker which manages the strategy.
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
diff --git a/src/pools/selection-strategies/dynamic-pool-worker-choice-strategy.ts b/src/pools/selection-strategies/dynamic-pool-worker-choice-strategy.ts
deleted file mode 100644 (file)
index 930675f..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-import type { IPoolInternal } from '../pool-internal'
-import type { IPoolWorker } from '../pool-worker'
-import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy'
-import type {
-  IWorkerChoiceStrategy,
-  WorkerChoiceStrategy
-} from './selection-strategies-types'
-import { WorkerChoiceStrategies } from './selection-strategies-types'
-import { getWorkerChoiceStrategy } from './selection-strategies-utils'
-
-/**
- * Selects the next worker for dynamic pool.
- *
- * @typeParam Worker - Type of worker which manages the strategy.
- * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
- * @typeParam Response - Type of response of execution. This can only be serializable data.
- */
-export class DynamicPoolWorkerChoiceStrategy<
-    Worker extends IPoolWorker,
-    Data,
-    Response
-  >
-  extends AbstractWorkerChoiceStrategy<Worker, Data, Response>
-  implements IWorkerChoiceStrategy {
-  private readonly workerChoiceStrategy: IWorkerChoiceStrategy
-
-  /**
-   * Constructs a worker choice strategy for dynamic pool.
-   *
-   * @param pool - The pool instance.
-   * @param createWorkerCallback - The worker creation callback for dynamic pool.
-   * @param workerChoiceStrategy - The worker choice strategy when the pool is busy.
-   */
-  public constructor (
-    pool: IPoolInternal<Worker, Data, Response>,
-    private readonly createWorkerCallback: () => number,
-    workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
-  ) {
-    super(pool)
-    this.workerChoiceStrategy = getWorkerChoiceStrategy(
-      this.pool,
-      workerChoiceStrategy
-    )
-    this.requiredStatistics = this.workerChoiceStrategy.requiredStatistics
-  }
-
-  /** {@inheritDoc} */
-  public reset (): boolean {
-    return this.workerChoiceStrategy.reset()
-  }
-
-  /** {@inheritDoc} */
-  public choose (): number {
-    if (this.pool.busy) {
-      return this.workerChoiceStrategy.choose()
-    }
-    if (!this.pool.full && this.pool.findFreeWorkerKey() === -1) {
-      return this.createWorkerCallback()
-    }
-    return this.workerChoiceStrategy.choose()
-  }
-
-  /** {@inheritDoc} */
-  public remove (workerKey: number): boolean {
-    return this.workerChoiceStrategy.remove(workerKey)
-  }
-}
index fb864c5b08ffc31a3cb1336225d31f285b0ce240..ed6b010397b57e7980c916452c526ab7bb546e66 100644 (file)
@@ -30,7 +30,7 @@ export const WorkerChoiceStrategies = Object.freeze({
 export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies
 
 /**
- * Pool tasks usage statistics requirements.
+ * Pool worker tasks usage statistics requirements.
  */
 export interface RequiredStatistics {
   runTime: boolean
index 1564e0f0f73e1c41d6da2f9d157620439f3489f8..f6e0feab2e8cb425fec1da9f420deba8b75135e1 100644 (file)
@@ -1,7 +1,6 @@
 import type { IPoolInternal } from '../pool-internal'
 import { PoolType } from '../pool-internal'
 import type { IPoolWorker } from '../pool-worker'
-import { DynamicPoolWorkerChoiceStrategy } from './dynamic-pool-worker-choice-strategy'
 import type {
   IWorkerChoiceStrategy,
   RequiredStatistics,
@@ -40,25 +39,6 @@ export class WorkerChoiceStrategyContext<
     this.setWorkerChoiceStrategy(workerChoiceStrategy)
   }
 
-  /**
-   * Gets the worker choice strategy instance specific to the pool type.
-   *
-   * @param workerChoiceStrategy - The worker choice strategy.
-   * @returns The worker choice strategy instance for the pool type.
-   */
-  private getPoolWorkerChoiceStrategy (
-    workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
-  ): IWorkerChoiceStrategy {
-    if (this.pool.type === PoolType.DYNAMIC) {
-      return new DynamicPoolWorkerChoiceStrategy(
-        this.pool,
-        this.createWorkerCallback,
-        workerChoiceStrategy
-      )
-    }
-    return getWorkerChoiceStrategy(this.pool, workerChoiceStrategy)
-  }
-
   /**
    * Gets the worker choice strategy required statistics.
    *
@@ -77,21 +57,30 @@ export class WorkerChoiceStrategyContext<
     workerChoiceStrategy: WorkerChoiceStrategy
   ): void {
     this.workerChoiceStrategy?.reset()
-    this.workerChoiceStrategy =
-      this.getPoolWorkerChoiceStrategy(workerChoiceStrategy)
+    this.workerChoiceStrategy = getWorkerChoiceStrategy(
+      this.pool,
+      workerChoiceStrategy
+    )
   }
 
   /**
-   * Chooses a worker with the underlying selection strategy.
+   * Chooses a worker with the worker choice strategy.
    *
    * @returns The key of the chosen one.
    */
   public execute (): number {
+    if (
+      this.pool.type === PoolType.DYNAMIC &&
+      !this.pool.full &&
+      this.pool.findFreeWorkerKey() === -1
+    ) {
+      return this.createWorkerCallback()
+    }
     return this.workerChoiceStrategy.choose()
   }
 
   /**
-   * Removes a worker in the underlying selection strategy internals.
+   * Removes a worker in the worker choice strategy internals.
    *
    * @param workerKey - The key of the worker to remove.
    * @returns `true` if the removal is successful, `false` otherwise.
index 3333aa6e0072c852ebc3eb7e857d2c1f0d9a3c89..33e42892c424a671caf547148aa4deacaf83f5f8 100644 (file)
@@ -7,7 +7,7 @@ import { FixedThreadPool } from './fixed'
  * A thread pool with a dynamic number of threads, but a guaranteed minimum number of threads.
  *
  * This thread pool creates new threads when the others are busy, up to the maximum number of threads.
- * When the maximum number of threads is reached, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
+ * When the maximum number of threads is reached and workers are busy, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
  *
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
  * @typeParam Response - Type of response of execution. This can only be serializable data.
index 7187a9007179bb5f7afbf511d055f8a5507d7fab..71a2819bc16eae97e10aff906e6e8349dfae1cc4 100644 (file)
@@ -172,13 +172,11 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy: WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN }
     )
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-        .nextWorkerId
+      pool.workerChoiceStrategyContext.workerChoiceStrategy.nextWorkerId
     ).toBeUndefined()
     pool.setWorkerChoiceStrategy(WorkerChoiceStrategies.ROUND_ROBIN)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-        .nextWorkerId
+      pool.workerChoiceStrategyContext.workerChoiceStrategy.nextWorkerId
     ).toBe(0)
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -464,7 +462,7 @@ describe('Selection strategies test suite', () => {
     // if (process.platform !== 'win32') {
     //   expect(
     //     pool.workerChoiceStrategyContext.workerChoiceStrategy
-    //       .workerChoiceStrategy.workerLastVirtualTaskTimestamp.size
+    //       .workerLastVirtualTaskTimestamp.size
     //   ).toBe(pool.workers.length)
     // }
     // We need to clean up the resources after our test
@@ -500,18 +498,18 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.js'
     )
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
+      pool.workerChoiceStrategyContext.workerChoiceStrategy
         .workerLastVirtualTaskTimestamp
     ).toBeUndefined()
     pool.setWorkerChoiceStrategy(WorkerChoiceStrategies.FAIR_SHARE)
-    for (const workerKey of pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy.workerLastVirtualTaskTimestamp.keys()) {
+    for (const workerKey of pool.workerChoiceStrategyContext.workerChoiceStrategy.workerLastVirtualTaskTimestamp.keys()) {
       expect(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy.workerLastVirtualTaskTimestamp.get(
+        pool.workerChoiceStrategyContext.workerChoiceStrategy.workerLastVirtualTaskTimestamp.get(
           workerKey
         ).start
       ).toBe(0)
       expect(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy.workerLastVirtualTaskTimestamp.get(
+        pool.workerChoiceStrategyContext.workerChoiceStrategy.workerLastVirtualTaskTimestamp.get(
           workerKey
         ).end
       ).toBe(0)
@@ -623,7 +621,7 @@ describe('Selection strategies test suite', () => {
     // TODO: Create a better test to cover `WeightedRoundRobinWorkerChoiceStrategy#choose`
     const promises = []
     const maxMultiplier =
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
+      pool.workerChoiceStrategyContext.workerChoiceStrategy
         .defaultWorkerWeight * 2
     for (let i = 0; i < max * maxMultiplier; i++) {
       promises.push(pool.execute())
@@ -631,8 +629,8 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     if (process.platform !== 'win32') {
       expect(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy
-          .workerChoiceStrategy.workersTaskRunTime.size
+        pool.workerChoiceStrategyContext.workerChoiceStrategy.workersTaskRunTime
+          .size
       ).toBe(pool.workers.length)
     }
     // We need to clean up the resources after our test
@@ -674,29 +672,24 @@ describe('Selection strategies test suite', () => {
       './tests/worker-files/thread/testWorker.js'
     )
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-        .currentWorkerId
+      pool.workerChoiceStrategyContext.workerChoiceStrategy.currentWorkerId
     ).toBeUndefined()
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-        .defaultWorkerWeight
+      pool.workerChoiceStrategyContext.workerChoiceStrategy.defaultWorkerWeight
     ).toBeUndefined()
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-        .workersTaskRunTime
+      pool.workerChoiceStrategyContext.workerChoiceStrategy.workersTaskRunTime
     ).toBeUndefined()
     pool.setWorkerChoiceStrategy(WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-        .currentWorkerId
+      pool.workerChoiceStrategyContext.workerChoiceStrategy.currentWorkerId
     ).toBe(0)
     expect(
-      pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-        .defaultWorkerWeight
+      pool.workerChoiceStrategyContext.workerChoiceStrategy.defaultWorkerWeight
     ).toBeGreaterThan(0)
-    for (const workerKey of pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy.workersTaskRunTime.keys()) {
+    for (const workerKey of pool.workerChoiceStrategyContext.workerChoiceStrategy.workersTaskRunTime.keys()) {
       expect(
-        pool.workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy.workersTaskRunTime.get(
+        pool.workerChoiceStrategyContext.workerChoiceStrategy.workersTaskRunTime.get(
           workerKey
         ).runTime
       ).toBe(0)
index 1f4b51cbf4d13956fd9ff5ef6c4550c3630e10f6..3f65fe8226d62c570f67f548205673960d729af4 100644 (file)
@@ -23,9 +23,6 @@ const {
 const {
   WeightedRoundRobinWorkerChoiceStrategy
 } = require('../../../lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy')
-const {
-  DynamicPoolWorkerChoiceStrategy
-} = require('../../../lib/pools/selection-strategies/dynamic-pool-worker-choice-strategy')
 
 describe('Worker choice strategy context test suite', () => {
   const min = 1
@@ -109,11 +106,8 @@ describe('Worker choice strategy context test suite', () => {
       WorkerChoiceStrategies.ROUND_ROBIN
     )
     expect(workerChoiceStrategyContext.workerChoiceStrategy).toBeInstanceOf(
-      DynamicPoolWorkerChoiceStrategy
+      RoundRobinWorkerChoiceStrategy
     )
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-    ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
   })
 
   it('Verify that setWorkerChoiceStrategy() works with LESS_USED and fixed pool', () => {
@@ -136,11 +130,8 @@ describe('Worker choice strategy context test suite', () => {
       WorkerChoiceStrategies.LESS_USED
     )
     expect(workerChoiceStrategyContext.workerChoiceStrategy).toBeInstanceOf(
-      DynamicPoolWorkerChoiceStrategy
+      LessUsedWorkerChoiceStrategy
     )
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-    ).toBeInstanceOf(LessUsedWorkerChoiceStrategy)
   })
 
   it('Verify that setWorkerChoiceStrategy() works with LESS_BUSY and fixed pool', () => {
@@ -163,11 +154,8 @@ describe('Worker choice strategy context test suite', () => {
       WorkerChoiceStrategies.LESS_BUSY
     )
     expect(workerChoiceStrategyContext.workerChoiceStrategy).toBeInstanceOf(
-      DynamicPoolWorkerChoiceStrategy
+      LessBusyWorkerChoiceStrategy
     )
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-    ).toBeInstanceOf(LessBusyWorkerChoiceStrategy)
   })
 
   it('Verify that setWorkerChoiceStrategy() works with FAIR_SHARE and fixed pool', () => {
@@ -190,11 +178,8 @@ describe('Worker choice strategy context test suite', () => {
       WorkerChoiceStrategies.FAIR_SHARE
     )
     expect(workerChoiceStrategyContext.workerChoiceStrategy).toBeInstanceOf(
-      DynamicPoolWorkerChoiceStrategy
+      FairShareWorkerChoiceStrategy
     )
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-    ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
   })
 
   it('Verify that setWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and fixed pool', () => {
@@ -217,10 +202,7 @@ describe('Worker choice strategy context test suite', () => {
       WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
     )
     expect(workerChoiceStrategyContext.workerChoiceStrategy).toBeInstanceOf(
-      DynamicPoolWorkerChoiceStrategy
+      WeightedRoundRobinWorkerChoiceStrategy
     )
-    expect(
-      workerChoiceStrategyContext.workerChoiceStrategy.workerChoiceStrategy
-    ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
   })
 })