Skip to content

Commit 288065c

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 296f907 commit 288065c

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
@@ -16,6 +16,7 @@ const {
1616
ObjectKeys,
1717
ObjectPrototypeHasOwnProperty,
1818
PromiseWithResolvers,
19+
SafeMap,
1920
SafeSet,
2021
StringPrototypeToUpperCase,
2122
Symbol,
@@ -453,8 +454,11 @@ const experimentalAlgorithms = [
453454
];
454455

455456
// Transform the algorithm definitions into the operation-keyed structure
457+
// Also builds a parallel Map<UPPERCASED_NAME, canonicalName> per operation
458+
// for O(1) case-insensitive algorithm name lookup in normalizeAlgorithm.
456459
functioncreateSupportedAlgorithms(algorithmDefs){
457460
constresult={};
461+
constnameMap={};
458462

459463
for(const{0: algorithmName,1: operations}ofObjectEntries(algorithmDefs)){
460464
// Skip algorithms that are conditionally not supported
@@ -465,6 +469,8 @@ function createSupportedAlgorithms(algorithmDefs) {
465469

466470
for(const{0: operation,1: dict}ofObjectEntries(operations)){
467471
result[operation]||={};
472+
nameMap[operation]||=newSafeMap();
473+
nameMap[operation].set(StringPrototypeToUpperCase(algorithmName),algorithmName);
468474

469475
// Add experimental warnings for experimental algorithms
470476
if(ArrayPrototypeIncludes(experimentalAlgorithms,algorithmName)){
@@ -482,10 +488,11 @@ function createSupportedAlgorithms(algorithmDefs) {
482488
}
483489
}
484490

485-
returnresult;
491+
return{algorithms: result, nameMap };
486492
}
487493

488-
constkSupportedAlgorithms=createSupportedAlgorithms(kAlgorithmDefinitions);
494+
const{algorithms: kSupportedAlgorithms,nameMap: kAlgorithmNameMap}=
495+
createSupportedAlgorithms(kAlgorithmDefinitions);
489496

490497
constsimpleAlgorithmDictionaries={
491498
AesCbcParams: {iv: 'BufferSource'},
@@ -527,6 +534,12 @@ const simpleAlgorithmDictionaries = {
527534
TurboShakeParams: {},
528535
};
529536

537+
// Pre-compute ObjectKeys() for each dictionary entry at module init
538+
// to avoid allocating a new keys array on every normalizeAlgorithm call.
539+
for(const{0: name,1: types}ofObjectEntries(simpleAlgorithmDictionaries)){
540+
simpleAlgorithmDictionaries[name]={keys: ObjectKeys(types), types };
541+
}
542+
530543
functionvalidateMaxBufferLength(data,name,max=kMaxBufferLength){
531544
if(data.byteLength>max){
532545
throwlazyDOMException(
@@ -537,6 +550,14 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
537550

538551
letwebidl;
539552

553+
// Keep this as a regular object. The WebIDL converters read and spread these
554+
// options on the normalizeAlgorithm hot path, and a null-prototype object
555+
// measurably regresses benchmark/misc/webcrypto-webidl normalizeAlgorithm-*.
556+
constkNormalizeAlgorithmOpts={
557+
prefix: 'Failed to normalize algorithm',
558+
context: 'passed algorithm',
559+
};
560+
540561
// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
541562
// adapted for Node.js from Deno's implementation
542563
// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195
@@ -549,69 +570,56 @@ function normalizeAlgorithm(algorithm, op) {
549570
// 1.
550571
constregisteredAlgorithms=kSupportedAlgorithms[op];
551572
// 2. 3.
552-
constinitialAlg=webidl.converters.Algorithm(algorithm,{
553-
prefix: 'Failed to normalize algorithm',
554-
context: 'passed algorithm',
555-
});
573+
constinitialAlg=webidl.converters.Algorithm(algorithm,
574+
kNormalizeAlgorithmOpts);
556575
// 4.
557576
letalgName=initialAlg.name;
558577

559-
// 5.
560-
letdesiredType;
561-
for(constkeyinregisteredAlgorithms){
562-
if(!ObjectPrototypeHasOwnProperty(registeredAlgorithms,key)){
563-
continue;
564-
}
565-
if(
566-
StringPrototypeToUpperCase(key)===StringPrototypeToUpperCase(algName)
567-
){
568-
algName=key;
569-
desiredType=registeredAlgorithms[key];
570-
}
571-
}
572-
if(desiredType===undefined)
578+
// 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
579+
constcanonicalName=kAlgorithmNameMap[op]?.get(
580+
StringPrototypeToUpperCase(algName));
581+
if(canonicalName===undefined)
573582
throwlazyDOMException('Unrecognized algorithm name','NotSupportedError');
574583

584+
algName=canonicalName;
585+
constdesiredType=registeredAlgorithms[algName];
586+
575587
// Fast path everything below if the registered dictionary is null
576588
if(desiredType===null)
577589
return{name: algName};
578590

579591
// 6.
580592
constnormalizedAlgorithm=webidl.converters[desiredType](
581593
{__proto__: algorithm,name: algName},
582-
{
583-
prefix: 'Failed to normalize algorithm',
584-
context: 'passed algorithm',
585-
},
594+
kNormalizeAlgorithmOpts,
586595
);
587596
// 7.
588597
normalizedAlgorithm.name=algName;
589598

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

0 commit comments

Comments
 (0)