docs: update benchmark versus external pools results
[poolifier.git] / benchmarks / benchmarks-utils.mjs
1 import crypto from 'node:crypto'
2 import assert from 'node:assert'
3 import fs from 'node:fs'
4 import Benchmark from 'benchmark'
5 import {
6 DynamicClusterPool,
7 DynamicThreadPool,
8 FixedClusterPool,
9 FixedThreadPool,
10 Measurements,
11 PoolTypes,
12 WorkerChoiceStrategies,
13 WorkerTypes
14 } from '../lib/index.mjs'
15 import { TaskFunctions } from './benchmarks-types.mjs'
16
17 export const buildPoolifierPool = (
18 workerType,
19 poolType,
20 poolSize,
21 poolOptions
22 ) => {
23 switch (poolType) {
24 case PoolTypes.fixed:
25 switch (workerType) {
26 case WorkerTypes.thread:
27 return new FixedThreadPool(
28 poolSize,
29 './benchmarks/internal/thread-worker.mjs',
30 poolOptions
31 )
32 case WorkerTypes.cluster:
33 return new FixedClusterPool(
34 poolSize,
35 './benchmarks/internal/cluster-worker.mjs',
36 poolOptions
37 )
38 }
39 break
40 case PoolTypes.dynamic:
41 switch (workerType) {
42 case WorkerTypes.thread:
43 return new DynamicThreadPool(
44 Math.floor(poolSize / 2),
45 poolSize,
46 './benchmarks/internal/thread-worker.mjs',
47 poolOptions
48 )
49 case WorkerTypes.cluster:
50 return new DynamicClusterPool(
51 Math.floor(poolSize / 2),
52 poolSize,
53 './benchmarks/internal/cluster-worker.mjs',
54 poolOptions
55 )
56 }
57 break
58 }
59 }
60
61 export const runPoolifierPool = async (
62 pool,
63 { taskExecutions, workerData }
64 ) => {
65 return await new Promise((resolve, reject) => {
66 let executions = 0
67 for (let i = 1; i <= taskExecutions; i++) {
68 pool
69 .execute(workerData)
70 .then(() => {
71 ++executions
72 if (executions === taskExecutions) {
73 resolve({ ok: 1 })
74 }
75 return null
76 })
77 .catch(err => {
78 console.error(err)
79 reject(err)
80 })
81 }
82 })
83 }
84
85 export const runPoolifierPoolBenchmark = async (
86 name,
87 pool,
88 { taskExecutions, workerData }
89 ) => {
90 return await new Promise((resolve, reject) => {
91 try {
92 const suite = new Benchmark.Suite(name)
93 for (const workerChoiceStrategy of Object.values(
94 WorkerChoiceStrategies
95 )) {
96 for (const enableTasksQueue of [false, true]) {
97 if (workerChoiceStrategy === WorkerChoiceStrategies.FAIR_SHARE) {
98 for (const measurement of [
99 Measurements.runTime,
100 Measurements.elu
101 ]) {
102 suite.add(
103 `${name} with ${workerChoiceStrategy}, with ${measurement} and ${
104 enableTasksQueue ? 'with' : 'without'
105 } tasks queue`,
106 async () => {
107 pool.setWorkerChoiceStrategy(workerChoiceStrategy, {
108 measurement
109 })
110 pool.enableTasksQueue(enableTasksQueue)
111 assert.strictEqual(
112 pool.opts.workerChoiceStrategy,
113 workerChoiceStrategy
114 )
115 assert.strictEqual(
116 pool.opts.enableTasksQueue,
117 enableTasksQueue
118 )
119 assert.strictEqual(
120 pool.opts.workerChoiceStrategyOptions.measurement,
121 measurement
122 )
123 await runPoolifierPool(pool, {
124 taskExecutions,
125 workerData
126 })
127 }
128 )
129 }
130 } else {
131 suite.add(
132 `${name} with ${workerChoiceStrategy} and ${
133 enableTasksQueue ? 'with' : 'without'
134 } tasks queue`,
135 async () => {
136 pool.setWorkerChoiceStrategy(workerChoiceStrategy)
137 pool.enableTasksQueue(enableTasksQueue)
138 assert.strictEqual(
139 pool.opts.workerChoiceStrategy,
140 workerChoiceStrategy
141 )
142 assert.strictEqual(pool.opts.enableTasksQueue, enableTasksQueue)
143 await runPoolifierPool(pool, {
144 taskExecutions,
145 workerData
146 })
147 }
148 )
149 }
150 }
151 }
152 suite
153 .on('cycle', event => {
154 console.info(event.target.toString())
155 })
156 .on('complete', async function () {
157 console.info(
158 'Fastest is ' +
159 LIST_FORMATTER.format(this.filter('fastest').map('name'))
160 )
161 await pool.destroy()
162 resolve()
163 })
164 .run({ async: true })
165 } catch (error) {
166 reject(error)
167 }
168 })
169 }
170
171 export const LIST_FORMATTER = new Intl.ListFormat('en-US', {
172 style: 'long',
173 type: 'conjunction'
174 })
175
176 export const generateRandomInteger = (
177 max = Number.MAX_SAFE_INTEGER,
178 min = 0
179 ) => {
180 if (max < min || max < 0 || min < 0) {
181 throw new RangeError('Invalid interval')
182 }
183 max = Math.floor(max)
184 if (min != null && min !== 0) {
185 min = Math.ceil(min)
186 return Math.floor(Math.random() * (max - min + 1)) + min
187 }
188 return Math.floor(Math.random() * (max + 1))
189 }
190
191 const jsonIntegerSerialization = n => {
192 for (let i = 0; i < n; i++) {
193 const o = {
194 a: i
195 }
196 JSON.stringify(o)
197 }
198 return { ok: 1 }
199 }
200
201 /**
202 * Intentionally inefficient implementation.
203 * @param {number} n - The number of fibonacci numbers to generate.
204 * @returns {number} - The nth fibonacci number.
205 */
206 const fibonacci = n => {
207 if (n <= 1) return n
208 return fibonacci(n - 1) + fibonacci(n - 2)
209 }
210
211 /**
212 * Intentionally inefficient implementation.
213 * @param {number} n - The number to calculate the factorial of.
214 * @returns {number} - The factorial of n.
215 */
216 const factorial = n => {
217 if (n === 0) {
218 return 1
219 }
220 return factorial(n - 1) * n
221 }
222
223 const readWriteFiles = (
224 n,
225 baseDirectory = `/tmp/poolifier-benchmarks/${crypto.randomInt(
226 281474976710655
227 )}`
228 ) => {
229 if (fs.existsSync(baseDirectory) === true) {
230 fs.rmSync(baseDirectory, { recursive: true })
231 }
232 fs.mkdirSync(baseDirectory, { recursive: true })
233 for (let i = 0; i < n; i++) {
234 const filePath = `${baseDirectory}/${i}`
235 fs.writeFileSync(filePath, i.toString(), {
236 encoding: 'utf8',
237 flag: 'a'
238 })
239 fs.readFileSync(filePath, 'utf8')
240 }
241 fs.rmSync(baseDirectory, { recursive: true })
242 return { ok: 1 }
243 }
244
245 export const executeTaskFunction = data => {
246 switch (data.function) {
247 case TaskFunctions.jsonIntegerSerialization:
248 return jsonIntegerSerialization(data.taskSize || 1000)
249 case TaskFunctions.fibonacci:
250 return fibonacci(data.taskSize || 1000)
251 case TaskFunctions.factorial:
252 return factorial(data.taskSize || 1000)
253 case TaskFunctions.readWriteFiles:
254 return readWriteFiles(data.taskSize || 1000)
255 default:
256 throw new Error('Unknown task function')
257 }
258 }