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 |
ab7bb4f8 JB |
20 | } = require('../lib/index.js') |
21 | const { TaskFunctions } = require('./benchmarks-types.js') | |
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, | |
ab7bb4f8 | 36 | './benchmarks/internal/cluster-worker.js', |
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, | |
ab7bb4f8 | 54 | './benchmarks/internal/cluster-worker.js', |
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 JB |
91 | const pool = buildPoolifierPool(workerType, poolType, poolSize) |
92 | const suite = new Benchmark.Suite(name) | |
cde5b54e | 93 | try { |
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 | ||
ab7bb4f8 | 189 | const generateRandomInteger = (max = Number.MAX_SAFE_INTEGER, min = 0) => { |
548140e6 | 190 | if (max < min || max < 0 || min < 0) { |
4af5c11a JB |
191 | throw new RangeError('Invalid interval') |
192 | } | |
c2d7d79b | 193 | max = Math.floor(max) |
4af5c11a | 194 | if (min != null && min !== 0) { |
c2d7d79b | 195 | min = Math.ceil(min) |
872585ea | 196 | return Math.floor(Math.random() * (max - min + 1)) + min |
74750c7f | 197 | } |
872585ea | 198 | return Math.floor(Math.random() * (max + 1)) |
74750c7f JB |
199 | } |
200 | ||
041dc05b | 201 | const jsonIntegerSerialization = n => { |
cdace0e5 JB |
202 | for (let i = 0; i < n; i++) { |
203 | const o = { | |
204 | a: i | |
205 | } | |
206 | JSON.stringify(o) | |
207 | } | |
30b963d4 | 208 | return { ok: 1 } |
cdace0e5 JB |
209 | } |
210 | ||
bdacc2d2 JB |
211 | /** |
212 | * Intentionally inefficient implementation. | |
7d82d90e JB |
213 | * @param {number} n - The number of fibonacci numbers to generate. |
214 | * @returns {number} - The nth fibonacci number. | |
bdacc2d2 | 215 | */ |
041dc05b | 216 | const fibonacci = n => { |
024daf59 | 217 | if (n <= 1) return n |
bdacc2d2 JB |
218 | return fibonacci(n - 1) + fibonacci(n - 2) |
219 | } | |
220 | ||
7d82d90e JB |
221 | /** |
222 | * Intentionally inefficient implementation. | |
7d82d90e JB |
223 | * @param {number} n - The number to calculate the factorial of. |
224 | * @returns {number} - The factorial of n. | |
225 | */ | |
041dc05b | 226 | const factorial = n => { |
7d82d90e JB |
227 | if (n === 0) { |
228 | return 1 | |
7d82d90e | 229 | } |
965415bb | 230 | return factorial(n - 1) * n |
7d82d90e JB |
231 | } |
232 | ||
bac873bd | 233 | const readWriteFiles = ( |
670734fc | 234 | n, |
4a8cea37 | 235 | baseDirectory = `/tmp/poolifier-benchmarks/${randomInt(281474976710655)}` |
bac873bd | 236 | ) => { |
4a8cea37 JB |
237 | if (existsSync(baseDirectory) === true) { |
238 | rmSync(baseDirectory, { recursive: true }) | |
cdace0e5 | 239 | } |
4a8cea37 | 240 | mkdirSync(baseDirectory, { recursive: true }) |
cdace0e5 JB |
241 | for (let i = 0; i < n; i++) { |
242 | const filePath = `${baseDirectory}/${i}` | |
4a8cea37 | 243 | writeFileSync(filePath, i.toString(), { |
cdace0e5 JB |
244 | encoding: 'utf8', |
245 | flag: 'a' | |
246 | }) | |
4a8cea37 | 247 | readFileSync(filePath, 'utf8') |
cdace0e5 | 248 | } |
4a8cea37 | 249 | rmSync(baseDirectory, { recursive: true }) |
30b963d4 | 250 | return { ok: 1 } |
cdace0e5 JB |
251 | } |
252 | ||
ab7bb4f8 | 253 | const executeTaskFunction = data => { |
2d2e32c2 | 254 | switch (data.function) { |
dbca3be9 | 255 | case TaskFunctions.jsonIntegerSerialization: |
d1a9aa41 | 256 | return jsonIntegerSerialization(data.taskSize || 1000) |
dbca3be9 | 257 | case TaskFunctions.fibonacci: |
d1a9aa41 | 258 | return fibonacci(data.taskSize || 1000) |
dbca3be9 | 259 | case TaskFunctions.factorial: |
d1a9aa41 | 260 | return factorial(data.taskSize || 1000) |
dbca3be9 | 261 | case TaskFunctions.readWriteFiles: |
cdace0e5 | 262 | return readWriteFiles(data.taskSize || 1000) |
2d2e32c2 | 263 | default: |
dbca3be9 | 264 | throw new Error('Unknown task function') |
2d2e32c2 JB |
265 | } |
266 | } | |
ab7bb4f8 JB |
267 | |
268 | module.exports = { | |
269 | LIST_FORMATTER, | |
ab7bb4f8 JB |
270 | executeTaskFunction, |
271 | generateRandomInteger, | |
272 | runPoolifierPoolBenchmark | |
273 | } |