Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: improve randomUUID performance #37243

Closed
134 changes: 56 additions & 78 deletions lib/internal/crypto/random.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
'use strict';

const {
Array,
BigInt,
FunctionPrototypeBind,
FunctionPrototypeCall,
MathMin,
NumberIsNaN,
NumberIsSafeInteger,
NumberPrototypeToString,
StringPrototypePadStart,
} = primordials;

const {
Expand Down Expand Up @@ -291,104 +294,79 @@ function getRandomValues(data) {
// Implements an RFC 4122 version 4 random UUID.
// To improve performance, random data is generated in batches
// large enough to cover kBatchSize UUID's at a time. The uuidData
// and uuid buffers are reused. Each call to randomUUID() consumes
// 16 bytes from the buffer.

const kHexDigits = [
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 97, 98, 99, 100, 101, 102,
];
// buffer is reused. Each call to randomUUID() consumes 16 bytes
// from the buffer.

const kBatchSize = 128;
let uuidData;
let uuidNotBuffered;
let uuid;
let uuidBatch = 0;

function getBufferedUUID() {
if (uuidData === undefined) {
uuidData = secureBuffer(16 * kBatchSize);
if (uuidData === undefined)
throw new ERR_OPERATION_FAILED('Out of memory');
let hexBytesCache;
function getHexBytes() {
if (hexBytesCache === undefined) {
hexBytesCache = new Array(256);
for (let i = 0; i < hexBytesCache.length; i++) {
const hex = NumberPrototypeToString(i, 16);
hexBytesCache[i] = StringPrototypePadStart(hex, 2, '0');
}
}
return hexBytesCache;
}

function serializeUUID(buf, offset = 0) {
const kHexBytes = getHexBytes();
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
return kHexBytes[buf[offset]] +
kHexBytes[buf[offset + 1]] +
kHexBytes[buf[offset + 2]] +
kHexBytes[buf[offset + 3]] +
'-' +
kHexBytes[buf[offset + 4]] +
kHexBytes[buf[offset + 5]] +
'-' +
kHexBytes[(buf[offset + 6] & 0x0f) | 0x40] +
kHexBytes[buf[offset + 7]] +
'-' +
kHexBytes[(buf[offset + 8] & 0x3f) | 0x80] +
kHexBytes[buf[offset + 9]] +
'-' +
kHexBytes[buf[offset + 10]] +
kHexBytes[buf[offset + 11]] +
kHexBytes[buf[offset + 12]] +
kHexBytes[buf[offset + 13]] +
kHexBytes[buf[offset + 14]] +
kHexBytes[buf[offset + 15]];
}

function getBufferedUUID() {
uuidData ??= secureBuffer(16 * kBatchSize);
if (uuidData === undefined)
throw new ERR_OPERATION_FAILED('Out of memory');

if (uuidBatch === 0) randomFillSync(uuidData);
uuidBatch = (uuidBatch + 1) % kBatchSize;
return uuidData.slice(uuidBatch * 16, (uuidBatch * 16) + 16);
return serializeUUID(uuidData, uuidBatch * 16);
}

function getUnbufferedUUID() {
uuidNotBuffered ??= secureBuffer(16);
if (uuidNotBuffered === undefined)
throw new ERR_OPERATION_FAILED('Out of memory');
randomFillSync(uuidNotBuffered);
return serializeUUID(uuidNotBuffered);
}

function randomUUID(options) {
if (options !== undefined)
validateObject(options, 'options');
const {
disableEntropyCache = false,
} = { ...options };
} = options || {};

validateBoolean(disableEntropyCache, 'options.disableEntropyCache');

if (uuid === undefined) {
uuid = Buffer.alloc(36, '-');
uuid[14] = 52; // '4', identifies the UUID version
}

let uuidBuf;
if (!disableEntropyCache) {
uuidBuf = getBufferedUUID();
} else {
uuidBuf = uuidNotBuffered;
if (uuidBuf === undefined)
uuidBuf = uuidNotBuffered = secureBuffer(16);
if (uuidBuf === undefined)
throw new ERR_OPERATION_FAILED('Out of memory');
randomFillSync(uuidBuf);
}

// Variant byte: 10xxxxxx (variant 1)
uuidBuf[8] = (uuidBuf[8] & 0x3f) | 0x80;

// This function is structured the way it is for performance.
// The uuid buffer stores the serialization of the random
// bytes from uuidData.
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
let n = 0;
uuid[0] = kHexDigits[uuidBuf[n] >> 4];
uuid[1] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[2] = kHexDigits[uuidBuf[n] >> 4];
uuid[3] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[4] = kHexDigits[uuidBuf[n] >> 4];
uuid[5] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[6] = kHexDigits[uuidBuf[n] >> 4];
uuid[7] = kHexDigits[uuidBuf[n++] & 0xf];
// -
uuid[9] = kHexDigits[uuidBuf[n] >> 4];
uuid[10] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[11] = kHexDigits[uuidBuf[n] >> 4];
uuid[12] = kHexDigits[uuidBuf[n++] & 0xf];
// -
// 4, uuid[14] is set already...
uuid[15] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[16] = kHexDigits[uuidBuf[n] >> 4];
uuid[17] = kHexDigits[uuidBuf[n++] & 0xf];
// -
uuid[19] = kHexDigits[uuidBuf[n] >> 4];
uuid[20] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[21] = kHexDigits[uuidBuf[n] >> 4];
uuid[22] = kHexDigits[uuidBuf[n++] & 0xf];
// -
uuid[24] = kHexDigits[uuidBuf[n] >> 4];
uuid[25] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[26] = kHexDigits[uuidBuf[n] >> 4];
uuid[27] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[28] = kHexDigits[uuidBuf[n] >> 4];
uuid[29] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[30] = kHexDigits[uuidBuf[n] >> 4];
uuid[31] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[32] = kHexDigits[uuidBuf[n] >> 4];
uuid[33] = kHexDigits[uuidBuf[n++] & 0xf];
uuid[34] = kHexDigits[uuidBuf[n] >> 4];
uuid[35] = kHexDigits[uuidBuf[n] & 0xf];

return uuid.latin1Slice(0, 36);
return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID();
}

function createRandomPrimeJob(type, size, options) {
Expand Down