|
1 | 1 | const { cpus } = require('os')
|
| 2 | +const { hrtime } = require('process') |
2 | 3 |
|
3 | 4 | const { hashSync, hash, compare, genSaltSync } = require('bcrypt')
|
4 | 5 | const { hashSync: hashSyncJs, hash: hashJs, compare: compareJs, genSaltSync: genSaltSyncJs } = require('bcryptjs')
|
5 | 6 | const { Suite } = require('benchmark')
|
6 | 7 | const chalk = require('chalk')
|
7 | 8 | const { range } = require('lodash')
|
| 9 | +const { from, timer, lastValueFrom, Subject } = require('rxjs') |
| 10 | +const { mergeMap, takeUntil } = require('rxjs/operators') |
8 | 11 |
|
9 | 12 | const { hash: napiHash, hashSync: napiHashSync, verify, genSaltSync: napiGenSaltSync } = require('../index')
|
10 | 13 |
|
11 |
| -const parallel = cpus().length |
| 14 | +const parallel = cpus().length - 1 |
12 | 15 |
|
13 | 16 | const password = 'node-rust-password'
|
14 | 17 |
|
15 |
| -function runAsync(round = 12) { |
16 |
| - const asyncHashSuite = new Suite(`Async hash round ${round}`) |
17 |
| - return new Promise((resolve) => { |
18 |
| - asyncHashSuite |
19 |
| - .add('@node-rs/bcrypt', { |
20 |
| - defer: true, |
21 |
| - fn: (deferred) => { |
22 |
| - Promise.all(range(parallel).map(() => napiHash(password, round))).then(() => { |
23 |
| - deferred.resolve() |
24 |
| - }) |
25 |
| - }, |
26 |
| - }) |
27 |
| - .add('node bcrypt', { |
28 |
| - defer: true, |
29 |
| - fn: (deferred) => { |
30 |
| - Promise.all(range(parallel).map(() => hash(password, round))).then(() => { |
31 |
| - deferred.resolve() |
32 |
| - }) |
33 |
| - }, |
34 |
| - }) |
35 |
| - .add('bcryptjs', { |
36 |
| - defer: true, |
37 |
| - fn: (deferred) => { |
38 |
| - Promise.all(range(parallel).map(() => hashJs(password, round))).then(() => { |
39 |
| - deferred.resolve() |
40 |
| - }) |
41 |
| - }, |
42 |
| - }) |
43 |
| - .on('cycle', function (event) { |
44 |
| - event.target.hz = event.target.hz * parallel |
45 |
| - console.info(String(event.target)) |
46 |
| - }) |
47 |
| - .on('complete', function () { |
48 |
| - console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
49 |
| - resolve() |
| 18 | +const CPU_LENGTH = cpus().length |
| 19 | + |
| 20 | +const DEFAULT_TOTAL_ITERATIONS = 10000 |
| 21 | +const DEFAULT_MAX_DURATION = 20000 |
| 22 | + |
| 23 | +function bench(name, options = {}) { |
| 24 | + const suites = [] |
| 25 | + return { |
| 26 | + add(suiteName, suiteFn) { |
| 27 | + suites.push({ |
| 28 | + name: suiteName, |
| 29 | + fn: suiteFn, |
50 | 30 | })
|
51 |
| - .run({ async: true }) |
52 |
| - }) |
| 31 | + return this |
| 32 | + }, |
| 33 | + run: async () => { |
| 34 | + let fastest = { |
| 35 | + perf: -1, |
| 36 | + name: '', |
| 37 | + } |
| 38 | + for (const { suiteName, fn: suiteFn } of suites) { |
| 39 | + try { |
| 40 | + await suiteFn() |
| 41 | + } catch (e) { |
| 42 | + console.error(`Warming up ${suiteName} failed`) |
| 43 | + throw e |
| 44 | + } |
| 45 | + } |
| 46 | + for (const { name: suiteName, fn: suiteFn } of suites) { |
| 47 | + const iterations = options.iterations ?? DEFAULT_TOTAL_ITERATIONS |
| 48 | + const parallel = options.parallel ?? CPU_LENGTH |
| 49 | + const maxDuration = options.maxDuration ?? DEFAULT_MAX_DURATION |
| 50 | + const start = hrtime.bigint() |
| 51 | + let totalIterations = 0 |
| 52 | + let finishedIterations = 0 |
| 53 | + const finish$ = new Subject() |
| 54 | + await lastValueFrom( |
| 55 | + from({ length: iterations }).pipe( |
| 56 | + mergeMap(async () => { |
| 57 | + totalIterations++ |
| 58 | + await suiteFn() |
| 59 | + finishedIterations++ |
| 60 | + if (finishedIterations === totalIterations) { |
| 61 | + finish$.next() |
| 62 | + finish$.complete() |
| 63 | + } |
| 64 | + }, parallel), |
| 65 | + takeUntil(timer(maxDuration)), |
| 66 | + ), |
| 67 | + ) |
| 68 | + if (finishedIterations !== totalIterations) { |
| 69 | + await lastValueFrom(finish$) |
| 70 | + } |
| 71 | + const duration = Number(hrtime.bigint() - start) |
| 72 | + const currentPerf = totalIterations / duration |
| 73 | + if (currentPerf > fastest.perf) { |
| 74 | + fastest = { |
| 75 | + perf: currentPerf, |
| 76 | + name: suiteName, |
| 77 | + } |
| 78 | + } |
| 79 | + console.info(`${suiteName} ${Math.round(currentPerf * 1e9)} ops/s`) |
| 80 | + } |
| 81 | + console.info(`In ${name} suite, fastest is ${fastest.name}`) |
| 82 | + }, |
| 83 | + } |
53 | 84 | }
|
54 | 85 |
|
55 |
| -runAsync() |
56 |
| - .then( |
57 |
| - () => |
58 |
| - new Promise((resolve) => { |
59 |
| - const suite = new Suite('Async verify') |
60 |
| - const hash = napiHashSync(password) |
61 |
| - suite |
62 |
| - .add({ |
63 |
| - name: '@node-rs/bcrypt', |
64 |
| - defer: true, |
65 |
| - fn: (deferred) => { |
66 |
| - Promise.all(range(parallel).map(() => verify(password, hash))).then(() => { |
67 |
| - deferred.resolve() |
68 |
| - }) |
69 |
| - }, |
70 |
| - }) |
71 |
| - .add({ |
72 |
| - name: 'node bcrypt', |
73 |
| - defer: true, |
74 |
| - fn: (deferred) => { |
75 |
| - Promise.all(range(parallel).map(() => compare(password, hash))).then(() => { |
76 |
| - deferred.resolve() |
77 |
| - }) |
78 |
| - }, |
79 |
| - }) |
80 |
| - .add({ |
81 |
| - name: 'bcryptjs', |
82 |
| - defer: true, |
83 |
| - fn: (deferred) => { |
84 |
| - Promise.all(range(parallel).map(() => compareJs(password, hash))).then(() => { |
85 |
| - deferred.resolve() |
86 |
| - }) |
87 |
| - }, |
88 |
| - }) |
89 |
| - .on('cycle', function (event) { |
90 |
| - event.target.hz = event.target.hz * parallel |
91 |
| - console.info(String(event.target)) |
92 |
| - }) |
93 |
| - .on('complete', function () { |
94 |
| - resolve() |
95 |
| - console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
96 |
| - }) |
97 |
| - .run() |
98 |
| - }), |
99 |
| - ) |
100 |
| - .then(() => { |
101 |
| - return new Promise((resolve) => { |
102 |
| - const syncHashSuite = new Suite(`Hash round 12`) |
103 |
| - syncHashSuite |
104 |
| - .add('@node-rs/bcrypt', () => { |
105 |
| - napiHashSync(password, 12) |
106 |
| - }) |
107 |
| - .add('node bcrypt', () => { |
108 |
| - hashSync(password, 12) |
109 |
| - }) |
110 |
| - .add('bcryptjs', () => { |
111 |
| - hashSyncJs(password, 12) |
112 |
| - }) |
113 |
| - .on('cycle', function (event) { |
114 |
| - console.info(String(event.target)) |
115 |
| - }) |
116 |
| - .on('complete', function () { |
117 |
| - console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
118 |
| - resolve() |
119 |
| - }) |
120 |
| - .run() |
121 |
| - }) |
122 |
| - }) |
123 |
| - .then(() => { |
124 |
| - return new Promise((resolve) => { |
125 |
| - new Suite('genSaltSync') |
126 |
| - .add('@node-rs/bcrypt', () => { |
127 |
| - napiGenSaltSync(10, '2b') |
128 |
| - }) |
129 |
| - .add('node bcrypt', () => { |
130 |
| - genSaltSync(10, 'b') |
131 |
| - }) |
132 |
| - .add('bcryptjs', () => { |
133 |
| - genSaltSyncJs(10) |
134 |
| - }) |
135 |
| - .on('cycle', function (event) { |
136 |
| - console.info(String(event.target)) |
137 |
| - }) |
138 |
| - .on('complete', function () { |
139 |
| - console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
140 |
| - resolve() |
141 |
| - }) |
142 |
| - .run() |
143 |
| - }) |
| 86 | +bench(`Hash round 12`) |
| 87 | + .add('@node-rs/bcrypt', () => napiHash(password, 12)) |
| 88 | + .add('node bcrypt', () => hash(password, 12)) |
| 89 | + .run() |
| 90 | + .catch((err) => { |
| 91 | + console.error(err) |
| 92 | + process.exit(1) |
144 | 93 | })
|
| 94 | + |
| 95 | +// function runAsync(round = 12) { |
| 96 | +// const asyncHashSuite = new Suite(`Async hash round ${round}`) |
| 97 | +// return new Promise((resolve) => { |
| 98 | +// asyncHashSuite |
| 99 | +// .add('@node-rs/bcrypt', { |
| 100 | +// defer: true, |
| 101 | +// fn: (deferred) => { |
| 102 | +// Promise.all(range(parallel).map(() => napiHash(password, round))).then(() => { |
| 103 | +// deferred.resolve() |
| 104 | +// }) |
| 105 | +// }, |
| 106 | +// }) |
| 107 | +// .add('node bcrypt', { |
| 108 | +// defer: true, |
| 109 | +// fn: (deferred) => { |
| 110 | +// Promise.all(range(parallel).map(() => hash(password, round))).then(() => { |
| 111 | +// deferred.resolve() |
| 112 | +// }) |
| 113 | +// }, |
| 114 | +// }) |
| 115 | +// .add('bcryptjs', { |
| 116 | +// defer: true, |
| 117 | +// fn: (deferred) => { |
| 118 | +// Promise.all(range(parallel).map(() => hashJs(password, round))).then(() => { |
| 119 | +// deferred.resolve() |
| 120 | +// }) |
| 121 | +// }, |
| 122 | +// }) |
| 123 | +// .on('cycle', function (event) { |
| 124 | +// event.target.hz = event.target.hz * parallel |
| 125 | +// console.info(String(event.target)) |
| 126 | +// }) |
| 127 | +// .on('complete', function () { |
| 128 | +// console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
| 129 | +// resolve() |
| 130 | +// }) |
| 131 | +// .run({ async: true }) |
| 132 | +// }) |
| 133 | +// } |
| 134 | + |
| 135 | +// runAsync() |
| 136 | +// .then( |
| 137 | +// () => |
| 138 | +// new Promise((resolve) => { |
| 139 | +// const suite = new Suite('Async verify') |
| 140 | +// const hash = napiHashSync(password) |
| 141 | +// suite |
| 142 | +// .add({ |
| 143 | +// name: '@node-rs/bcrypt', |
| 144 | +// defer: true, |
| 145 | +// fn: (deferred) => { |
| 146 | +// Promise.all(range(parallel).map(() => verify(password, hash))).then(() => { |
| 147 | +// deferred.resolve() |
| 148 | +// }) |
| 149 | +// }, |
| 150 | +// }) |
| 151 | +// .add({ |
| 152 | +// name: 'node bcrypt', |
| 153 | +// defer: true, |
| 154 | +// fn: (deferred) => { |
| 155 | +// Promise.all(range(parallel).map(() => compare(password, hash))).then(() => { |
| 156 | +// deferred.resolve() |
| 157 | +// }) |
| 158 | +// }, |
| 159 | +// }) |
| 160 | +// .add({ |
| 161 | +// name: 'bcryptjs', |
| 162 | +// defer: true, |
| 163 | +// fn: (deferred) => { |
| 164 | +// Promise.all(range(parallel).map(() => compareJs(password, hash))).then(() => { |
| 165 | +// deferred.resolve() |
| 166 | +// }) |
| 167 | +// }, |
| 168 | +// }) |
| 169 | +// .on('cycle', function (event) { |
| 170 | +// event.target.hz = event.target.hz * parallel |
| 171 | +// console.info(String(event.target)) |
| 172 | +// }) |
| 173 | +// .on('complete', function () { |
| 174 | +// resolve() |
| 175 | +// console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
| 176 | +// }) |
| 177 | +// .run() |
| 178 | +// }), |
| 179 | +// ) |
| 180 | +// .then(() => { |
| 181 | +// return new Promise((resolve) => { |
| 182 | +// const syncHashSuite = new Suite(`Hash round 12`) |
| 183 | +// syncHashSuite |
| 184 | +// .add('@node-rs/bcrypt', () => { |
| 185 | +// napiHashSync(password, 12) |
| 186 | +// }) |
| 187 | +// .add('node bcrypt', () => { |
| 188 | +// hashSync(password, 12) |
| 189 | +// }) |
| 190 | +// .add('bcryptjs', () => { |
| 191 | +// hashSyncJs(password, 12) |
| 192 | +// }) |
| 193 | +// .on('cycle', function (event) { |
| 194 | +// console.info(String(event.target)) |
| 195 | +// }) |
| 196 | +// .on('complete', function () { |
| 197 | +// console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
| 198 | +// resolve() |
| 199 | +// }) |
| 200 | +// .run() |
| 201 | +// }) |
| 202 | +// }) |
| 203 | +// .then(() => { |
| 204 | +// return new Promise((resolve) => { |
| 205 | +// new Suite('genSaltSync') |
| 206 | +// .add('@node-rs/bcrypt', () => { |
| 207 | +// napiGenSaltSync(10, '2b') |
| 208 | +// }) |
| 209 | +// .add('node bcrypt', () => { |
| 210 | +// genSaltSync(10, 'b') |
| 211 | +// }) |
| 212 | +// .add('bcryptjs', () => { |
| 213 | +// genSaltSyncJs(10) |
| 214 | +// }) |
| 215 | +// .on('cycle', function (event) { |
| 216 | +// console.info(String(event.target)) |
| 217 | +// }) |
| 218 | +// .on('complete', function () { |
| 219 | +// console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`) |
| 220 | +// resolve() |
| 221 | +// }) |
| 222 | +// .run() |
| 223 | +// }) |
| 224 | +// }) |
0 commit comments