Merge pull request #20726 from Snuffleupagus/getOrInsertComputed-fewer-functions

Reduce allocations and function creation when using `getOrInsert` and `getOrInsertComputed`
This commit is contained in:
Tim van der Meij 2026-02-24 23:32:36 +01:00 committed by GitHub
commit 4ecbd0cbe2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 73 additions and 47 deletions

View File

@ -18,6 +18,7 @@ import {
assert, assert,
BaseException, BaseException,
hexNumbers, hexNumbers,
makeArr,
objectSize, objectSize,
stringToPDFString, stringToPDFString,
Util, Util,
@ -669,7 +670,9 @@ function getNewAnnotationsMap(annotationStorage) {
if (!key.startsWith(AnnotationEditorPrefix)) { if (!key.startsWith(AnnotationEditorPrefix)) {
continue; continue;
} }
newAnnotationsByPage.getOrInsert(value.pageIndex, []).push(value); newAnnotationsByPage
.getOrInsertComputed(value.pageIndex, makeArr)
.push(value);
} }
return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null; return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null;
} }

View File

@ -20,6 +20,7 @@ import {
info, info,
InvalidPDFException, InvalidPDFException,
isArrayEqual, isArrayEqual,
makeArr,
objectSize, objectSize,
PageActionEventType, PageActionEventType,
RenderingIntentFlag, RenderingIntentFlag,
@ -1894,7 +1895,7 @@ class PDFDocument {
orphanFields.put(fieldRef, parentRef); orphanFields.put(fieldRef, parentRef);
} }
promises.getOrInsert(name, []).push( promises.getOrInsertComputed(name, makeArr).push(
AnnotationFactory.create( AnnotationFactory.create(
xref, xref,
fieldRef, fieldRef,

View File

@ -15,6 +15,7 @@
import { import {
AnnotationPrefix, AnnotationPrefix,
makeArr,
stringToPDFString, stringToPDFString,
stringToUTF8String, stringToUTF8String,
warn, warn,
@ -450,7 +451,7 @@ class StructTreeRoot {
for (const element of elements) { for (const element of elements) {
if (element.structTreeParentId) { if (element.structTreeParentId) {
const id = parseInt(element.structTreeParentId.split("_mc")[1], 10); const id = parseInt(element.structTreeParentId.split("_mc")[1], 10);
idToElements.getOrInsert(id, []).push(element); idToElements.getOrInsertComputed(id, makeArr).push(element);
} }
} }

View File

@ -24,10 +24,10 @@ import {
$resolvePrototypes, $resolvePrototypes,
$root, $root,
} from "./symbol_utils.js"; } from "./symbol_utils.js";
import { makeArr, warn } from "../../shared/util.js";
import { NamespaceSetUp } from "./setup.js"; import { NamespaceSetUp } from "./setup.js";
import { Template } from "./template.js"; import { Template } from "./template.js";
import { UnknownNamespace } from "./unknown.js"; import { UnknownNamespace } from "./unknown.js";
import { warn } from "../../shared/util.js";
import { XFAObject } from "./xfa_object.js"; import { XFAObject } from "./xfa_object.js";
class Root extends XFAObject { class Root extends XFAObject {
@ -166,12 +166,9 @@ class Builder {
_addNamespacePrefix(prefixes) { _addNamespacePrefix(prefixes) {
for (const { prefix, value } of prefixes) { for (const { prefix, value } of prefixes) {
const namespace = this._searchNamespace(value); const namespace = this._searchNamespace(value);
let prefixStack = this._namespacePrefixes.get(prefix); this._namespacePrefixes
if (!prefixStack) { .getOrInsertComputed(prefix, makeArr)
prefixStack = []; .push(namespace);
this._namespacePrefixes.set(prefix, prefixStack);
}
prefixStack.push(namespace);
} }
} }

View File

@ -13,9 +13,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { makeObj, warn } from "../../shared/util.js";
import { $globalData } from "./symbol_utils.js"; import { $globalData } from "./symbol_utils.js";
import { stripQuotes } from "./utils.js"; import { stripQuotes } from "./utils.js";
import { warn } from "../../shared/util.js";
class FontFinder { class FontFinder {
constructor(pdfFonts) { constructor(pdfFonts) {
@ -48,14 +48,9 @@ class FontFinder {
addPdfFont(pdfFont) { addPdfFont(pdfFont) {
const cssFontInfo = pdfFont.cssFontInfo; const cssFontInfo = pdfFont.cssFontInfo;
const name = cssFontInfo.fontFamily; const name = cssFontInfo.fontFamily;
let font = this.fonts.get(name); const font = this.fonts.getOrInsertComputed(name, makeObj);
if (!font) { this.defaultFont ??= font;
font = Object.create(null);
this.fonts.set(name, font);
if (!this.defaultFont) {
this.defaultFont = font;
}
}
let property = ""; let property = "";
const fontWeight = parseFloat(cssFontInfo.fontWeight); const fontWeight = parseFloat(cssFontInfo.fontWeight);
if (parseFloat(cssFontInfo.italicAngle) !== 0) { if (parseFloat(cssFontInfo.italicAngle) !== 0) {

View File

@ -19,7 +19,7 @@ import {
$getChildrenByName, $getChildrenByName,
$getParent, $getParent,
} from "./symbol_utils.js"; } from "./symbol_utils.js";
import { warn } from "../../shared/util.js"; import { makeMap, warn } from "../../shared/util.js";
const namePattern = /^[^.[]+/; const namePattern = /^[^.[]+/;
const indexPattern = /^[^\]]+/; const indexPattern = /^[^\]]+/;
@ -193,11 +193,7 @@ function searchNode(
let children, cached; let children, cached;
if (useCache) { if (useCache) {
cached = somCache.get(node); cached = somCache.getOrInsertComputed(node, makeMap);
if (!cached) {
cached = new Map();
somCache.set(node, cached);
}
children = cached.get(cacheName); children = cached.get(cacheName);
} }

View File

@ -36,6 +36,7 @@ import {
AnnotationType, AnnotationType,
FeatureTest, FeatureTest,
LINE_FACTOR, LINE_FACTOR,
makeArr,
shadow, shadow,
unreachable, unreachable,
Util, Util,
@ -3877,7 +3878,9 @@ class AnnotationLayer {
this.#elements.push(element); this.#elements.push(element);
if (data.popupRef) { if (data.popupRef) {
popupToElements.getOrInsert(data.popupRef, []).push(element); popupToElements
.getOrInsertComputed(data.popupRef, makeArr)
.push(element);
} }
} }

View File

@ -13,7 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { shadow, unreachable } from "../shared/util.js"; import { makeMap, shadow, unreachable } from "../shared/util.js";
import { AnnotationEditor } from "./editor/editor.js"; import { AnnotationEditor } from "./editor/editor.js";
import { MurmurHash3_64 } from "../shared/murmurhash3.js"; import { MurmurHash3_64 } from "../shared/murmurhash3.js";
@ -260,7 +260,7 @@ class AnnotationStorage {
if (key === "type") { if (key === "type") {
continue; continue;
} }
const counters = map.getOrInsertComputed(key, () => new Map()); const counters = map.getOrInsertComputed(key, makeMap);
counters.set(val, (counters.get(val) ?? 0) + 1); counters.set(val, (counters.get(val) ?? 0) + 1);
} }
} }

View File

@ -25,6 +25,7 @@ import {
getVerbosityLevel, getVerbosityLevel,
info, info,
isNodeJS, isNodeJS,
makeObj,
MathClamp, MathClamp,
RenderingIntentFlag, RenderingIntentFlag,
setVerbosityLevel, setVerbosityLevel,
@ -1502,8 +1503,9 @@ class PDFPageProxy {
optionalContentConfigPromise ||= optionalContentConfigPromise ||=
this._transport.getOptionalContentConfig(renderingIntent); this._transport.getOptionalContentConfig(renderingIntent);
const intentState = this._intentStates.getOrInsertComputed(cacheKey, () => const intentState = this._intentStates.getOrInsertComputed(
Object.create(null) cacheKey,
makeObj
); );
// Ensure that a pending `streamReader` cancel timeout is always aborted. // Ensure that a pending `streamReader` cancel timeout is always aborted.
if (intentState.streamReaderCancelTimeout) { if (intentState.streamReaderCancelTimeout) {
@ -1675,7 +1677,7 @@ class PDFPageProxy {
); );
const intentState = this._intentStates.getOrInsertComputed( const intentState = this._intentStates.getOrInsertComputed(
intentArgs.cacheKey, intentArgs.cacheKey,
() => Object.create(null) makeObj
); );
let opListTask; let opListTask;

View File

@ -22,6 +22,7 @@ import {
FONT_IDENTITY_MATRIX, FONT_IDENTITY_MATRIX,
ImageKind, ImageKind,
info, info,
makeMap,
OPS, OPS,
shadow, shadow,
TextRenderingMode, TextRenderingMode,
@ -989,10 +990,7 @@ class CanvasGraphics {
: [currentTransform.slice(0, 4), fillColor] : [currentTransform.slice(0, 4), fillColor]
); );
cache = this._cachedBitmapsMap.getOrInsertComputed( cache = this._cachedBitmapsMap.getOrInsertComputed(mainKey, makeMap);
mainKey,
() => new Map()
);
const cachedImage = cache.get(cacheKey); const cachedImage = cache.get(cacheKey);
if (cachedImage && !isPatternFill) { if (cachedImage && !isPatternFill) {
const offsetX = Math.round( const offsetX = Math.round(

View File

@ -15,6 +15,11 @@
const INITIAL_DATA = Symbol("INITIAL_DATA"); const INITIAL_DATA = Symbol("INITIAL_DATA");
const dataObj = () => ({
...Promise.withResolvers(),
data: INITIAL_DATA,
});
/** /**
* A PDF document and page is built of many objects. E.g. there are objects for * A PDF document and page is built of many objects. E.g. there are objects for
* fonts, images, rendering code, etc. These objects may get processed inside of * fonts, images, rendering code, etc. These objects may get processed inside of
@ -30,10 +35,7 @@ class PDFObjects {
* @returns {Object} * @returns {Object}
*/ */
#ensureObj(objId) { #ensureObj(objId) {
return this.#objs.getOrInsertComputed(objId, () => ({ return this.#objs.getOrInsertComputed(objId, dataObj);
...Promise.withResolvers(),
data: INITIAL_DATA,
}));
} }
/** /**

View File

@ -33,6 +33,9 @@ import {
getUuid, getUuid,
ImageKind, ImageKind,
InvalidPDFException, InvalidPDFException,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
normalizeUnicode, normalizeUnicode,
OPS, OPS,
@ -123,6 +126,9 @@ globalThis.pdfjsLib = {
isDataScheme, isDataScheme,
isPdfFile, isPdfFile,
isValidExplicitDest, isValidExplicitDest,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
noContextMenu, noContextMenu,
normalizeUnicode, normalizeUnicode,
@ -182,6 +188,9 @@ export {
isDataScheme, isDataScheme,
isPdfFile, isPdfFile,
isValidExplicitDest, isValidExplicitDest,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
noContextMenu, noContextMenu,
normalizeUnicode, normalizeUnicode,

View File

@ -1234,6 +1234,12 @@ function _isValidExplicitDest(validRef, validName, dest) {
return true; return true;
} }
// Helpers for simple `Map.prototype.getOrInsertComputed()` invocations,
// to avoid duplicate function creation.
const makeArr = () => [];
const makeMap = () => new Map();
const makeObj = () => Object.create(null);
// TODO: Replace all occurrences of this function with `Math.clamp` once // TODO: Replace all occurrences of this function with `Math.clamp` once
// https://github.com/tc39/proposal-math-clamp/ is generally available. // https://github.com/tc39/proposal-math-clamp/ is generally available.
function MathClamp(v, min, max) { function MathClamp(v, min, max) {
@ -1331,6 +1337,9 @@ export {
isNodeJS, isNodeJS,
LINE_DESCENT_FACTOR, LINE_DESCENT_FACTOR,
LINE_FACTOR, LINE_FACTOR,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
MeshFigureType, MeshFigureType,
normalizeUnicode, normalizeUnicode,

View File

@ -24,6 +24,9 @@ import {
getUuid, getUuid,
ImageKind, ImageKind,
InvalidPDFException, InvalidPDFException,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
normalizeUnicode, normalizeUnicode,
OPS, OPS,
@ -107,6 +110,9 @@ const expectedAPI = Object.freeze({
isDataScheme, isDataScheme,
isPdfFile, isPdfFile,
isValidExplicitDest, isValidExplicitDest,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
noContextMenu, noContextMenu,
normalizeUnicode, normalizeUnicode,

View File

@ -33,6 +33,7 @@ import {
AnnotationEditorType, AnnotationEditorType,
AnnotationEditorUIManager, AnnotationEditorUIManager,
AnnotationMode, AnnotationMode,
makeArr,
MathClamp, MathClamp,
PermissionFlag, PermissionFlag,
PixelsPerInch, PixelsPerInch,
@ -2281,7 +2282,7 @@ class PDFViewer {
if (percent === 0 || widthPercent < 100) { if (percent === 0 || widthPercent < 100) {
continue; continue;
} }
pageLayout.getOrInsert(y, []).push(id); pageLayout.getOrInsertComputed(y, makeArr).push(id);
} }
// Find the row of the current page. // Find the row of the current page.
for (const yArray of pageLayout.values()) { for (const yArray of pageLayout.values()) {

View File

@ -44,6 +44,9 @@ const {
isDataScheme, isDataScheme,
isPdfFile, isPdfFile,
isValidExplicitDest, isValidExplicitDest,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
noContextMenu, noContextMenu,
normalizeUnicode, normalizeUnicode,
@ -103,6 +106,9 @@ export {
isDataScheme, isDataScheme,
isPdfFile, isPdfFile,
isValidExplicitDest, isValidExplicitDest,
makeArr,
makeMap,
makeObj,
MathClamp, MathClamp,
noContextMenu, noContextMenu,
normalizeUnicode, normalizeUnicode,

View File

@ -15,7 +15,7 @@
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */ /** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
import { FeatureTest, shadow } from "pdfjs-lib"; import { FeatureTest, makeMap, shadow } from "pdfjs-lib";
import { removeNullCharacters } from "./ui_utils.js"; import { removeNullCharacters } from "./ui_utils.js";
const PDF_ROLE_TO_HTML_ROLE = { const PDF_ROLE_TO_HTML_ROLE = {
@ -251,12 +251,9 @@ class StructTreeLayerBuilder {
const label = removeNullCharacters(alt); const label = removeNullCharacters(alt);
for (const child of structElement.children) { for (const child of structElement.children) {
if (child.type === "annotation") { if (child.type === "annotation") {
let attrs = this.#elementAttributes.get(child.id); this.#elementAttributes
if (!attrs) { .getOrInsertComputed(child.id, makeMap)
attrs = new Map(); .set("aria-label", label);
this.#elementAttributes.set(child.id, attrs);
}
attrs.set("aria-label", label);
added = true; added = true;
} }
} }