Skip to content

Commit 8bb5360

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 9623d9a commit 8bb5360

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
functioncreateSupportedAlgorithms(algorithmDefs){
459462
constresult={};
463+
constnameMap={};
460464

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

468472
for(const{0: operation,1: dict}ofObjectEntries(operations)){
469473
result[operation]||={};
474+
nameMap[operation]||=newSafeMap();
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-
returnresult;
493+
return{algorithms: result, nameMap };
488494
}
489495

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

492499
constsimpleAlgorithmDictionaries={
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}ofObjectEntries(simpleAlgorithmDictionaries)){
542+
simpleAlgorithmDictionaries[name]={keys: ObjectKeys(types), types };
543+
}
544+
532545
functionvalidateMaxBufferLength(data,name,max=kMaxBufferLength){
533546
if(data.byteLength>max){
534547
throwlazyDOMException(
@@ -539,6 +552,14 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
539552

540553
letwebidl;
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+
constkNormalizeAlgorithmOpts={
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
constregisteredAlgorithms=kSupportedAlgorithms[op];
553574
// 2. 3.
554-
constinitialAlg=webidl.converters.Algorithm(algorithm,{
555-
prefix: 'Failed to normalize algorithm',
556-
context: 'passed algorithm',
557-
});
575+
constinitialAlg=webidl.converters.Algorithm(algorithm,
576+
kNormalizeAlgorithmOpts);
558577
// 4.
559578
letalgName=initialAlg.name;
560579

561-
// 5.
562-
letdesiredType;
563-
for(constkeyinregisteredAlgorithms){
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+
constcanonicalName=kAlgorithmNameMap[op]?.get(
582+
StringPrototypeToUpperCase(algName));
583+
if(canonicalName===undefined)
575584
throwlazyDOMException('Unrecognized algorithm name','NotSupportedError');
576585

586+
algName=canonicalName;
587+
constdesiredType=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
constnormalizedAlgorithm=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-
constdict=simpleAlgorithmDictionaries[desiredType];
594-
// 10.
595-
constdictKeys=dict ? ObjectKeys(dict) : [];
596-
for(leti=0;i<dictKeys.length;i++){
597-
constmember=dictKeys[i];
598-
if(!ObjectPrototypeHasOwnProperty(dict,member))
599-
continue;
600-
constidlType=dict[member];
601-
constidlValue=normalizedAlgorithm[member];
602-
// 3.
603-
if(idlType==='BufferSource'&&idlValue){
604-
constisView=ArrayBufferIsView(idlValue);
605-
normalizedAlgorithm[member]=TypedArrayPrototypeSlice(
606-
newUint8Array(
607-
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
608-
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
609-
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
610-
),
611-
);
612-
}elseif(idlType==='HashAlgorithmIdentifier'){
613-
normalizedAlgorithm[member]=normalizeAlgorithm(idlValue,'digest');
614-
}elseif(idlType==='AlgorithmIdentifier'){
615-
// This extension point is not used by any supported algorithm (yet?)
616-
throwlazyDOMException('Not implemented.','NotSupportedError');
601+
// 9. 10. Pre-computed keys and types from simpleAlgorithmDictionaries.
602+
constdictMeta=simpleAlgorithmDictionaries[desiredType];
603+
if(dictMeta){
604+
const{keys: dictKeys,types: dictTypes}=dictMeta;
605+
for(leti=0;i<dictKeys.length;i++){
606+
constmember=dictKeys[i];
607+
constidlType=dictTypes[member];
608+
constidlValue=normalizedAlgorithm[member];
609+
// 3.
610+
if(idlType==='BufferSource'&&idlValue){
611+
constisView=ArrayBufferIsView(idlValue);
612+
normalizedAlgorithm[member]=TypedArrayPrototypeSlice(
613+
newUint8Array(
614+
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
615+
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
616+
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
617+
),
618+
);
619+
}elseif(idlType==='HashAlgorithmIdentifier'){
620+
normalizedAlgorithm[member]=normalizeAlgorithm(idlValue,'digest');
621+
}elseif(idlType==='AlgorithmIdentifier'){
622+
// This extension point is not used by any supported algorithm (yet?)
623+
throwlazyDOMException('Not implemented.','NotSupportedError');
624+
}
617625
}
618626
}
619627

0 commit comments

Comments
 (0)