| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0db1a1e commit 960c6be
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,38 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common.js'); | ||
| 4 | + const { randomInt } = require('crypto'); | ||
| 5 | + | ||
| 6 | + const bench = common.createBenchmark(main, { | ||
| 7 | + mode: ['sync', 'async-sequential', 'async-parallel'], | ||
| 8 | + min: [-(2 ** 47) + 1, -10_000, -100], | ||
| 9 | + max: [100, 10_000, 2 ** 47], | ||
| 10 | + n: [1e3, 1e5] | ||
| 11 | + }); | ||
| 12 | + | ||
| 13 | + function main({ mode, min, max, n }) { | ||
| 14 | + if (mode === 'sync') { | ||
| 15 | + bench.start(); | ||
| 16 | + for (let i = 0; i < n; i++) | ||
| 17 | + randomInt(min, max); | ||
| 18 | + bench.end(n); | ||
| 19 | + } else if (mode === 'async-sequential') { | ||
| 20 | + bench.start(); | ||
| 21 | + (function next(i) { | ||
| 22 | + if (i === n) | ||
| 23 | + return bench.end(n); | ||
| 24 | + randomInt(min, max, () => { | ||
| 25 | + next(i + 1); | ||
| 26 | + }); | ||
| 27 | + })(0); | ||
| 28 | + } else { | ||
| 29 | + bench.start(); | ||
| 30 | + let done = 0; | ||
| 31 | + for (let i = 0; i < n; i++) { | ||
| 32 | + randomInt(min, max, () => { | ||
| 33 | + if (++done === n) | ||
| 34 | + bench.end(n); | ||
| 35 | + }); | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,10 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | 4 | Array, | |
| 5 | + ArrayPrototypeForEach, | ||
| 6 | + ArrayPrototypePush, | ||
| 7 | + ArrayPrototypeShift, | ||
| 8 | + ArrayPrototypeSplice, | ||
| 5 | 9 | BigInt, | |
| 6 | 10 | FunctionPrototypeBind, | |
| 7 | 11 | FunctionPrototypeCall, | |
@@ -186,6 +190,13 @@ function randomFill(buf, offset, size, callback) { | |||
| 186 | 190 | // e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6); | |
| 187 | 191 | const RAND_MAX = 0xFFFF_FFFF_FFFF; | |
| 188 | 192 | ||
| 193 | + // Cache random data to use in randomInt. The cache size must be evenly | ||
| 194 | + // divisible by 6 because each attempt to obtain a random int uses 6 bytes. | ||
| 195 | + const randomCache = new FastBuffer(6 * 1024); | ||
| 196 | + let randomCacheOffset = randomCache.length; | ||
| 197 | + let asyncCacheFillInProgress = false; | ||
| 198 | + const asyncCachePendingTasks = []; | ||
| 199 | + | ||
| 189 | 200 | // Generates an integer in [min, max) range where min is inclusive and max is | |
| 190 | 201 | // exclusive. | |
| 191 | 202 | function randomInt(min, max, callback) { | |
@@ -230,33 +241,59 @@ function randomInt(min, max, callback) { | |||
| 230 | 241 | // than or equal to 0 and less than randLimit. | |
| 231 | 242 | const randLimit = RAND_MAX - (RAND_MAX % range); | |
| 232 | 243 | ||
| 233 | - if (isSync) { | ||
| 234 | - // Sync API | ||
| 235 | - while (true) { | ||
| 236 | - const x = randomBytes(6).readUIntBE(0, 6); | ||
| 237 | - if (x >= randLimit) { | ||
| 238 | - // Try again. | ||
| 239 | - continue; | ||
| 240 | - } | ||
| 241 | - return (x % range) + min; | ||
| 244 | + // If we don't have a callback, or if there is still data in the cache, we can | ||
| 245 | + // do this synchronously, which is super fast. | ||
| 246 | + while (isSync || (randomCacheOffset < randomCache.length)) { | ||
| 247 | + if (randomCacheOffset === randomCache.length) { | ||
| 248 | + // This might block the thread for a bit, but we are in sync mode. | ||
| 249 | + randomFillSync(randomCache); | ||
| 250 | + randomCacheOffset = 0; | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + const x = randomCache.readUIntBE(randomCacheOffset, 6); | ||
| 254 | + randomCacheOffset += 6; | ||
| 255 | + | ||
| 256 | + if (x < randLimit) { | ||
| 257 | + const n = (x % range) + min; | ||
| 258 | + if (isSync) return n; | ||
| 259 | + process.nextTick(callback, undefined, n); | ||
| 260 | + return; | ||
| 242 | 261 | } | |
| 243 | - } else { | ||
| 244 | - // Async API | ||
| 245 | - const pickAttempt = () => { | ||
| 246 | - randomBytes(6, (err, bytes) => { | ||
| 247 | - if (err) return callback(err); | ||
| 248 | - const x = bytes.readUIntBE(0, 6); | ||
| 249 | - if (x >= randLimit) { | ||
| 250 | - // Try again. | ||
| 251 | - return pickAttempt(); | ||
| 252 | - } | ||
| 253 | - const n = (x % range) + min; | ||
| 254 | - callback(null, n); | ||
| 255 | - }); | ||
| 256 | - }; | ||
| 257 | - | ||
| 258 | - pickAttempt(); | ||
| 259 | 262 | } | |
| 263 | + | ||
| 264 | + // At this point, we are in async mode with no data in the cache. We cannot | ||
| 265 | + // simply refill the cache, because another async call to randomInt might | ||
| 266 | + // already be doing that. Instead, queue this call for when the cache has | ||
| 267 | + // been refilled. | ||
| 268 | + ArrayPrototypePush(asyncCachePendingTasks, { min, max, callback }); | ||
| 269 | + asyncRefillRandomIntCache(); | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + function asyncRefillRandomIntCache() { | ||
| 273 | + if (asyncCacheFillInProgress) | ||
| 274 | + return; | ||
| 275 | + | ||
| 276 | + asyncCacheFillInProgress = true; | ||
| 277 | + randomFill(randomCache, (err) => { | ||
| 278 | + asyncCacheFillInProgress = false; | ||
| 279 | + | ||
| 280 | + const tasks = asyncCachePendingTasks; | ||
| 281 | + const errorReceiver = err && ArrayPrototypeShift(tasks); | ||
| 282 | + if (!err) | ||
| 283 | + randomCacheOffset = 0; | ||
| 284 | + | ||
| 285 | + // Restart all pending tasks. If an error occurred, we only notify a single | ||
| 286 | + // callback (errorReceiver) about it. This way, every async call to | ||
| 287 | + // randomInt has a chance of being successful, and it avoids complex | ||
| 288 | + // exception handling here. | ||
| 289 | + ArrayPrototypeForEach(ArrayPrototypeSplice(tasks, 0), (task) => { | ||
| 290 | + randomInt(task.min, task.max, task.callback); | ||
| 291 | + }); | ||
| 292 | + | ||
| 293 | + // This is the only call that might throw, and is therefore done at the end. | ||
| 294 | + if (errorReceiver) | ||
| 295 | + errorReceiver.callback(err); | ||
| 296 | + }); | ||
| 260 | 297 | } | |
| 261 | 298 | ||
| 262 | 299 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments