Skip to content

Commit 7607ad4

Browse files
panvaaduh95
authored andcommitted
crypto: optimize normalizeAlgorithm dispatch hot path
Replace the O(n) case-insensitive algorithm-name scan with an O(1) SafeMap lookup. The map is pre-built at module init alongside kSupportedAlgorithms. Hoist the opts object literal used in normalizeAlgorithm to module level to avoid allocating identical { prefix, context } objects on every call. Pre-compute ObjectKeys() for simpleAlgorithmDictionaries entries at module init to avoid allocating a new keys array on every normalizeAlgorithm call. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #62756 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 9de263a commit 7607ad4

1 file changed

Lines changed: 57 additions & 49 deletions

File tree

lib/internal/crypto/util.js

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
PromisePrototypeThen,
1818
PromiseReject,
1919
PromiseWithResolvers,
20+
SafeMap,
2021
SafeSet,
2122
StringPrototypeToUpperCase,
2223
Symbol,
@@ -455,8 +456,11 @@ const experimentalAlgorithms = [
455456
];
456457

457458
// Transform the algorithm definitions into the operation-keyed structure
459+
// Also builds a parallel Map<UPPERCASED_NAME, canonicalName> per operation
460+
// for O(1) case-insensitive algorithm name lookup in normalizeAlgorithm.
458461
function createSupportedAlgorithms(algorithmDefs) {
459462
const result = {};
463+
const nameMap = {};
460464

461465
for (const { 0: algorithmName, 1: operations } of ObjectEntries(algorithmDefs)) {
462466
// Skip algorithms that are conditionally not supported
@@ -467,6 +471,8 @@ function createSupportedAlgorithms(algorithmDefs) {
467471

468472
for (const { 0: operation, 1: dict } of ObjectEntries(operations)) {
469473
result[operation] ||= {};
474+
nameMap[operation] ||= new SafeMap();
475+
nameMap[operation].set(StringPrototypeToUpperCase(algorithmName), algorithmName);
470476

471477
// Add experimental warnings for experimental algorithms
472478
if (ArrayPrototypeIncludes(experimentalAlgorithms, algorithmName)) {
@@ -484,10 +490,11 @@ function createSupportedAlgorithms(algorithmDefs) {
484490
}
485491
}
486492

487-
return result;
493+
return { algorithms: result, nameMap };
488494
}
489495

490-
const kSupportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
496+
const { algorithms: kSupportedAlgorithms, nameMap: kAlgorithmNameMap } =
497+
createSupportedAlgorithms(kAlgorithmDefinitions);
491498

492499
const simpleAlgorithmDictionaries = {
493500
AesCbcParams: { iv: 'BufferSource' },
@@ -529,6 +536,12 @@ const simpleAlgorithmDictionaries = {
529536
TurboShakeParams: {},
530537
};
531538

539+
// Pre-compute ObjectKeys() for each dictionary entry at module init
540+
// to avoid allocating a new keys array on every normalizeAlgorithm call.
541+
for (const { 0: name, 1: types } of ObjectEntries(simpleAlgorithmDictionaries)) {
542+
simpleAlgorithmDictionaries[name] = { keys: ObjectKeys(types), types };
543+
}
544+
532545
function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
533546
if (data.byteLength > max) {
534547
throw lazyDOMException(
@@ -539,6 +552,14 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
539552

540553
let webidl;
541554

555+
// Keep this as a regular object. The WebIDL converters read and spread these
556+
// options on the normalizeAlgorithm hot path, and a null-prototype object
557+
// measurably regresses benchmark/misc/webcrypto-webidl normalizeAlgorithm-*.
558+
const kNormalizeAlgorithmOpts = {
559+
prefix: 'Failed to normalize algorithm',
560+
context: 'passed algorithm',
561+
};
562+
542563
// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
543564
// adapted for Node.js from Deno's implementation
544565
// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195
@@ -551,69 +572,56 @@ function normalizeAlgorithm(algorithm, op) {
551572
// 1.
552573
const registeredAlgorithms = kSupportedAlgorithms[op];
553574
// 2. 3.
554-
const initialAlg = webidl.converters.Algorithm(algorithm, {
555-
prefix: 'Failed to normalize algorithm',
556-
context: 'passed algorithm',
557-
});
575+
const initialAlg = webidl.converters.Algorithm(algorithm,
576+
kNormalizeAlgorithmOpts);
558577
// 4.
559578
let algName = initialAlg.name;
560579

561-
// 5.
562-
let desiredType;
563-
for (const key in registeredAlgorithms) {
564-
if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) {
565-
continue;
566-
}
567-
if (
568-
StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName)
569-
) {
570-
algName = key;
571-
desiredType = registeredAlgorithms[key];
572-
}
573-
}
574-
if (desiredType === undefined)
580+
// 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
581+
const canonicalName = kAlgorithmNameMap[op]?.get(
582+
StringPrototypeToUpperCase(algName));
583+
if (canonicalName === undefined)
575584
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
576585

586+
algName = canonicalName;
587+
const desiredType = registeredAlgorithms[algName];
588+
577589
// Fast path everything below if the registered dictionary is null
578590
if (desiredType === null)
579591
return { name: algName };
580592

581593
// 6.
582594
const normalizedAlgorithm = webidl.converters[desiredType](
583595
{ __proto__: algorithm, name: algName },
584-
{
585-
prefix: 'Failed to normalize algorithm',
586-
context: 'passed algorithm',
587-
},
596+
kNormalizeAlgorithmOpts,
588597
);
589598
// 7.
590599
normalizedAlgorithm.name = algName;
591600

592-
// 9.
593-
const dict = simpleAlgorithmDictionaries[desiredType];
594-
// 10.
595-
const dictKeys = dict ? ObjectKeys(dict) : [];
596-
for (let i = 0; i < dictKeys.length; i++) {
597-
const member = dictKeys[i];
598-
if (!ObjectPrototypeHasOwnProperty(dict, member))
599-
continue;
600-
const idlType = dict[member];
601-
const idlValue = normalizedAlgorithm[member];
602-
// 3.
603-
if (idlType === 'BufferSource' && idlValue) {
604-
const isView = ArrayBufferIsView(idlValue);
605-
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
606-
new Uint8Array(
607-
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
608-
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
609-
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
610-
),
611-
);
612-
} else if (idlType === 'HashAlgorithmIdentifier') {
613-
normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest');
614-
} else if (idlType === 'AlgorithmIdentifier') {
615-
// This extension point is not used by any supported algorithm (yet?)
616-
throw lazyDOMException('Not implemented.', 'NotSupportedError');
601+
// 9. 10. Pre-computed keys and types from simpleAlgorithmDictionaries.
602+
const dictMeta = simpleAlgorithmDictionaries[desiredType];
603+
if (dictMeta) {
604+
const { keys: dictKeys, types: dictTypes } = dictMeta;
605+
for (let i = 0; i < dictKeys.length; i++) {
606+
const member = dictKeys[i];
607+
const idlType = dictTypes[member];
608+
const idlValue = normalizedAlgorithm[member];
609+
// 3.
610+
if (idlType === 'BufferSource' && idlValue) {
611+
const isView = ArrayBufferIsView(idlValue);
612+
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
613+
new Uint8Array(
614+
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
615+
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
616+
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
617+
),
618+
);
619+
} else if (idlType === 'HashAlgorithmIdentifier') {
620+
normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest');
621+
} else if (idlType === 'AlgorithmIdentifier') {
622+
// This extension point is not used by any supported algorithm (yet?)
623+
throw lazyDOMException('Not implemented.', 'NotSupportedError');
624+
}
617625
}
618626
}
619627

0 commit comments

Comments
 (0)