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