Skip to content

Commit 68fd87a

Browse files
committed
Add global caching, for /Resources without blend modes, and use it to reduce repeated fetching/parsing in PartialEvaluator.hasBlendModes
The `PartialEvaluator.hasBlendModes` method is necessary to determine if there's any blend modes on a page, which unfortunately requires *synchronous* parsing of the /Resources of each page before its rendering can start (see the "StartRenderPage"-message). In practice it's not uncommon for certain /Resources-entries to be found on more than one page (referenced via the XRef-table), which thus leads to unnecessary re-fetching/re-parsing of data in `PartialEvaluator.hasBlendModes`. To improve performance, especially in pathological cases, we can cache /Resources-entries when it's absolutely clear that they do not contain *any* blend modes at all[1]. This way, subsequent `PartialEvaluator.hasBlendModes` calls can be made significantly more efficient. This patch was tested using the PDF file from issue 6961, i.e. https://github.com/mozilla/pdf.js/files/121712/test.pdf: ``` [ { "id": "issue6961", "file": "../web/pdfs/issue6961.pdf", "md5": "a80e4357a8fda758d96c2c76f2980b03", "rounds": 100, "type": "eq" } ] ``` which gave the following results when comparing this patch against the `master` branch: ``` -- Grouped By browser, page, stat -- browser | page | stat | Count | Baseline(ms) | Current(ms) | +/- | % | Result(P<.05) ------- | ---- | ------------ | ----- | ------------ | ----------- | ---- | ------ | ------------- firefox | 0 | Overall | 100 | 1034 | 555 | -480 | -46.39 | faster firefox | 0 | Page Request | 100 | 489 | 7 | -482 | -98.67 | faster firefox | 0 | Rendering | 100 | 545 | 548 | 2 | 0.45 | firefox | 1 | Overall | 100 | 912 | 428 | -484 | -53.06 | faster firefox | 1 | Page Request | 100 | 487 | 1 | -486 | -99.77 | faster firefox | 1 | Rendering | 100 | 425 | 427 | 2 | 0.51 | ``` --- [1] In the case where blend modes *are* found, it becomes a lot more difficult to know if it's generally safe to skip them. Hence we don't cache anything in that case, however note that most document/pages do not utilize blend modes anyway.
1 parent 8b652d6 commit 68fd87a

File tree

4 files changed

+47
-5
lines changed

4 files changed

+47
-5
lines changed

src/core/document.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class Page {
7676
fontCache,
7777
builtInCMapCache,
7878
globalImageCache,
79+
nonBlendModesSet,
7980
}) {
8081
this.pdfManager = pdfManager;
8182
this.pageIndex = pageIndex;
@@ -85,6 +86,7 @@ class Page {
8586
this.fontCache = fontCache;
8687
this.builtInCMapCache = builtInCMapCache;
8788
this.globalImageCache = globalImageCache;
89+
this.nonBlendModesSet = nonBlendModesSet;
8890
this.evaluatorOptions = pdfManager.evaluatorOptions;
8991
this.resourcesPromise = null;
9092

@@ -312,7 +314,10 @@ class Page {
312314
const opList = new OperatorList(intent, sink);
313315

314316
handler.send("StartRenderPage", {
315-
transparency: partialEvaluator.hasBlendModes(this.resources),
317+
transparency: partialEvaluator.hasBlendModes(
318+
this.resources,
319+
this.nonBlendModesSet
320+
),
316321
pageIndex: this.pageIndex,
317322
intent,
318323
});
@@ -917,6 +922,7 @@ class PDFDocument {
917922
fontCache: catalog.fontCache,
918923
builtInCMapCache: catalog.builtInCMapCache,
919924
globalImageCache: catalog.globalImageCache,
925+
nonBlendModesSet: catalog.nonBlendModesSet,
920926
});
921927
}));
922928
}

src/core/evaluator.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,15 @@ class PartialEvaluator {
239239
return newEvaluator;
240240
}
241241

242-
hasBlendModes(resources) {
242+
hasBlendModes(resources, nonBlendModesSet) {
243243
if (!(resources instanceof Dict)) {
244244
return false;
245245
}
246+
if (resources.objId && nonBlendModesSet.has(resources.objId)) {
247+
return false;
248+
}
246249

247-
const processed = new RefSet();
250+
const processed = new RefSet(nonBlendModesSet);
248251
if (resources.objId) {
249252
processed.put(resources.objId);
250253
}
@@ -344,6 +347,15 @@ class PartialEvaluator {
344347
}
345348
}
346349
}
350+
351+
if (processed.size > 0) {
352+
// When no blend modes exist, there's no need check any of the parsed
353+
// `Ref`s again for subsequent pages. This helps reduce redundant
354+
// `XRef.fetch` calls for some documents (e.g. issue6961.pdf).
355+
processed.forEach(ref => {
356+
nonBlendModesSet.put(ref);
357+
});
358+
}
347359
return false;
348360
}
349361

src/core/obj.js

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class Catalog {
7676
this.builtInCMapCache = new Map();
7777
this.globalImageCache = new GlobalImageCache();
7878
this.pageKidsCountCache = new RefSetCache();
79+
this.nonBlendModesSet = new RefSet();
7980
}
8081

8182
get version() {
@@ -937,6 +938,7 @@ class Catalog {
937938
clearPrimitiveCaches();
938939
this.globalImageCache.clear(/* onlyData = */ manuallyTriggered);
939940
this.pageKidsCountCache.clear();
941+
this.nonBlendModesSet.clear();
940942

941943
const promises = [];
942944
this.fontCache.forEach(function (promise) {

src/core/primitives.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,20 @@ var Ref = (function RefClosure() {
277277
// The reference is identified by number and generation.
278278
// This structure stores only one instance of the reference.
279279
class RefSet {
280-
constructor() {
281-
this._set = new Set();
280+
constructor(parent = null) {
281+
if (
282+
(typeof PDFJSDev === "undefined" ||
283+
PDFJSDev.test("!PRODUCTION || TESTING")) &&
284+
parent &&
285+
!(parent instanceof RefSet)
286+
) {
287+
unreachable('RefSet: Invalid "parent" value.');
288+
}
289+
this._set = new Set(parent && parent._set);
290+
}
291+
292+
get size() {
293+
return this._set.size;
282294
}
283295

284296
has(ref) {
@@ -292,6 +304,16 @@ class RefSet {
292304
remove(ref) {
293305
this._set.delete(ref.toString());
294306
}
307+
308+
forEach(callback) {
309+
for (const ref of this._set) {
310+
callback(ref);
311+
}
312+
}
313+
314+
clear() {
315+
this._set.clear();
316+
}
295317
}
296318

297319
class RefSetCache {

0 commit comments

Comments
 (0)