perf(性能提升): 优化字体并发与复杂页渲染

This commit is contained in:
xiaoqidun committed 2026-10-26 14:42:35 +08:00
1 parent 15a532a1ca
commit 04d7d2d672
3 files changed
+52 -20

No files matched your search

+43 -18
View File
@@ -64,16 +64,16 @@ class BrowserScheduler {
if (data.id == null || data.type) return; if (data.id == null || data.type) return;
const key = requestKey(worker, data); const key = requestKey(worker, data);
const started = worker.requests.get(key); const started = worker.requests.get(key);
const elapsed = data.elapsed ?? (started === undefined ? 0 : performance.now() - started);
const operation = worker.operations.get(key), owner = this.owner(worker, data); const operation = worker.operations.get(key), owner = this.owner(worker, data);
if (started !== undefined && data.ok && owner) { if (worker.primary && started !== undefined && data.ok && owner) {
const elapsed = performance.now() - started;
if (operation?.name === "ofdgoOpen") owner.openTime = elapsed; if (operation?.name === "ofdgoOpen") owner.openTime = elapsed;
if (operation?.name === "ofdgoRenderPage") owner.pageTime = owner.pageTime ? owner.pageTime * 0.75 + elapsed * 0.25 : elapsed; if (operation?.name === "ofdgoRenderPage") owner.pageTime = owner.pageTime ? owner.pageTime * 0.75 + elapsed * 0.25 : elapsed;
} }
worker.requests.delete(key); worker.requests.delete(key);
worker.operations.delete(key); worker.operations.delete(key);
if (this.memory > this.budget) this.limit = Math.max(1, this.limit - 1); if (this.memory > this.budget) this.limit = Math.max(1, this.limit - 1);
else if (started !== undefined && data.ok && performance.now() - started > 100 && this.memory + Math.max(128 << 20, worker.memory) < this.budget) this.limit = Math.min(this.capacity, this.limit + 1); else if (started !== undefined && data.ok && elapsed > 100 && this.memory + Math.max(128 << 20, worker.memory) < this.budget) this.limit = Math.min(this.capacity, this.limit + 1);
} }
drain() { drain() {
@@ -300,8 +300,9 @@ class BrowserEngine {
const data = event.data; const data = event.data;
if (data.memory) this.memory = data.memory; if (data.memory) this.memory = data.memory;
if (this.source) this.parallelism = engineScheduler.limit; if (this.source) this.parallelism = engineScheduler.limit;
if (data.id != null && !data.type) this.requests.delete(data.id); const rendered = data.id != null && !data.type && this.requests.delete(data.id);
this.onmessage?.(event); this.onmessage?.(event);
if (rendered && data.ok) this.dispatchPages();
}; };
this.primary.onerror = event => this.onerror?.(event); this.primary.onerror = event => this.onerror?.(event);
this.primary.onmessageerror = event => this.onmessageerror?.(event); this.primary.onmessageerror = event => this.onmessageerror?.(event);
@@ -407,6 +408,36 @@ class BrowserEngine {
}); });
} }
pageSlot(data) {
const preferred = [...this.slots].find(slot => !slot.busy && slot.pages.has(data.args[0]));
const pending = [...this.requests.values()].filter(slot => slot === null).length - Number(this.requests.has(data.id));
if (!preferred && pending <= 0) return null;
let slot = preferred || [...this.slots].filter(slot => !slot.busy).sort((a,b) => Math.abs(a.lastPage-data.args[0])-Math.abs(b.lastPage-data.args[0]))[0];
const worthwhile = (this.primary.pageTime || 0) * pending >= Math.max(50, this.startupTime || this.primary.openTime || 0);
if (!slot && worthwhile && this.foreground && !this.pagesPaused && this.slots.size < this.parallelism - 1
&& engineScheduler.memory + Math.max(128 << 20, this.memory) <= this.budget) slot = this.createSlot(this.source);
return slot;
}
dispatchPages() {
if (this.closed || !this.source || !this.foreground || this.pagesPaused) return;
const draining = engineScheduler.draining;
engineScheduler.draining = true;
try {
const queued = engineScheduler.queue.filter(task => task.worker === this.primary.runtime && task.data.engineID === this.engineID).sort((a,b) => a.order-b.order);
for (const task of queued) {
if (task.data.name !== "ofdgoRenderPage") break;
if (this.requests.get(task.data.id) !== null) continue;
let slot;
try { slot = this.pageSlot(task.data); } catch { break; }
if (!slot) break;
engineScheduler.queue.splice(engineScheduler.queue.indexOf(task), 1);
this.primary.requests.delete(task.data.id);
this.renderSlot(slot, task.data, task.transfer);
}
} finally { engineScheduler.draining = draining; }
}
postMessage(data, transfer) { postMessage(data, transfer) {
if (this.closed) return; if (this.closed) return;
if (!data.type && data.preload && this.pagesPaused) { if (!data.type && data.preload && this.pagesPaused) {
@@ -415,7 +446,7 @@ class BrowserEngine {
} }
if (data.type === "cancel") { if (data.type === "cancel") {
const slot = this.requests.get(data.id); const slot = this.requests.get(data.id);
if (slot) this.closeSlot(slot); if (slot && slot !== this.primary) this.closeSlot(slot);
else this.primary.postMessage(data); else this.primary.postMessage(data);
return; return;
} }
@@ -430,23 +461,16 @@ class BrowserEngine {
"ofdgoPreparePrint", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF", "ofdgoDiscardPrint", "ofdgoPreparePrint", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF", "ofdgoDiscardPrint",
"ofdgoExportAttachment", "ofdgoExportResource", "ofdgoExportMedia", "ofdgoExportVersion" "ofdgoExportAttachment", "ofdgoExportResource", "ofdgoExportMedia", "ofdgoExportVersion"
].includes(data.name)) this.invalidatePages(); ].includes(data.name)) this.invalidatePages();
const preferred = data.name === "ofdgoRenderPage" ? [...this.slots].find(slot => !slot.busy && slot.pages.has(data.args[0])) : null; const slot = data.name === "ofdgoRenderPage" && this.source ? this.pageSlot(data) : null;
if (data.name !== "ofdgoRenderPage" || !this.source || !preferred && ![...this.requests.values()].includes(null)) { if (!slot) {
if (data.name === "ofdgoRenderPage") this.requests.set(data.id, null); if (data.name === "ofdgoRenderPage") this.requests.set(data.id, null);
this.primary.postMessage(data, transfer); this.primary.postMessage(data, transfer);
return; return;
} }
let slot = preferred || [...this.slots].filter(slot => !slot.busy).sort((a,b) => Math.abs(a.lastPage-data.args[0])-Math.abs(b.lastPage-data.args[0]))[0]; this.renderSlot(slot, data, transfer);
const memory = engineScheduler.memory;
const pending = [...this.requests.values()].filter(slot => slot === null).length;
const worthwhile = (this.primary.pageTime || 0) * pending >= Math.max(50, this.startupTime || this.primary.openTime || 0);
if (!slot && worthwhile && this.foreground && !this.pagesPaused
&& this.slots.size < this.parallelism - 1 && memory + Math.max(128 << 20, this.memory) <= this.budget) slot = this.createSlot(this.source);
if (!slot) {
this.requests.set(data.id, null);
this.primary.postMessage(data, transfer);
return;
} }
renderSlot(slot, data, transfer) {
slot.busy = true; slot.busy = true;
this.requests.set(data.id, slot); this.requests.set(data.id, slot);
const revision = this.revision; const revision = this.revision;
@@ -462,7 +486,7 @@ class BrowserEngine {
if (!this.closed && revision === this.revision && error.name !== "AbortError") { if (!this.closed && revision === this.revision && error.name !== "AbortError") {
this.closeSlot(slot, error); this.closeSlot(slot, error);
this.parallelism = Math.max(1, this.slots.size + 1); this.parallelism = Math.max(1, this.slots.size + 1);
this.requests.set(data.id, null); this.requests.set(data.id, this.primary);
fallback = true; fallback = true;
this.primary.postMessage(data, transfer); this.primary.postMessage(data, transfer);
return; return;
@@ -474,6 +498,7 @@ class BrowserEngine {
slot.worker.reservation = 0; slot.worker.reservation = 0;
const memory = engineScheduler.memory; const memory = engineScheduler.memory;
if (memory > this.budget) { this.closeSlot(slot); this.parallelism = Math.max(1, this.slots.size + 1); } if (memory > this.budget) { this.closeSlot(slot); this.parallelism = Math.max(1, this.slots.size + 1); }
this.dispatchPages();
engineScheduler.drain(); engineScheduler.drain();
}); });
} }
+7 -2
View File
@@ -535,6 +535,10 @@ export function createPageView(context) {
const results = await Promise.allSettled(fonts.slice(offset, offset + 4).map(async font => { const results = await Promise.allSettled(fonts.slice(offset, offset + 4).map(async font => {
if (state.svgFonts.has(font.name)) return; if (state.svgFonts.has(font.name)) return;
const key = `${openSeq}:${loadSeq}:${font.name}`; const key = `${openSeq}:${loadSeq}:${font.name}`;
while (current() && !state.svgFonts.has(font.name) && !state.svgFontLoads.has(key) && state.svgFontLoads.size >= 4) {
await Promise.race([...state.svgFontLoads.values()].map(loading => loading.catch(() => {})));
}
if (!current() || state.svgFonts.has(font.name)) return;
let loading = state.svgFontLoads.get(key); let loading = state.svgFontLoads.get(key);
if (!loading) { if (!loading) {
loading = pageView.loadSVGFont(font, openSeq, loadSeq); loading = pageView.loadSVGFont(font, openSeq, loadSeq);
@@ -615,8 +619,9 @@ export function createPageView(context) {
if (task.shell && pageView.pageShell(task.index) === task.shell && !task.shell.classList.contains("rendered") if (task.shell && pageView.pageShell(task.index) === task.shell && !task.shell.classList.contains("rendered")
&& (!state.pageObserver || state.visiblePages.has(task.index) || task.index === state.pageIndex)) { && (!state.pageObserver || state.visiblePages.has(task.index) || task.index === state.pageIndex)) {
task.text ||= pageView.createTextLayerParts(task.page.text); task.text ||= pageView.createTextLayerParts(task.page.text);
const text = task.text.next(); let text;
if (!text.done) continue; do { text = task.text.next(); } while (!text.done && performance.now() - started < 6);
if (!text.done) break;
pageView.mountPageSVG(task.index, task.page, task.openSeq, text.value); pageView.mountPageSVG(task.index, task.page, task.openSeq, text.value);
} }
if (task.thumb && context.el.pageList.querySelector(`[data-page-index="${task.index}"] .thumb-paper`) === task.thumb) pageView.updateThumbnail(task.index, task.openSeq); if (task.thumb && context.el.pageList.querySelector(`[data-page-index="${task.index}"] .thumb-paper`) === task.thumb) pageView.updateThumbnail(task.index, task.openSeq);
+2
View File
@@ -337,9 +337,11 @@ async function handleMessage(engine, { id, name, args }) {
if (!saving && args.some(value => value?.mode !== undefined)) args.push(done => finish(done)); if (!saving && args.some(value => value?.mode !== undefined)) args.push(done => finish(done));
} }
} }
const started = name === "ofdgoOpen" || name === "ofdgoRenderPage" ? performance.now() : null;
const payload = await globalThis.ofdgoEngineCall(engine.id, name, ...args); const payload = await globalThis.ofdgoEngineCall(engine.id, name, ...args);
signal?.throwIfAborted(); signal?.throwIfAborted();
const result = typeof payload === "string" ? JSON.parse(payload) : payload; const result = typeof payload === "string" ? JSON.parse(payload) : payload;
if (started !== null && result.ok) result.elapsed = performance.now() - started;
if (cancellable && !importing && !converting && !resources && result.ok) { if (cancellable && !importing && !converting && !resources && result.ok) {
result.data.size = size; result.data.size = size;
if (name !== "ofdgoExportMedia") postMessage({ id, type: "export", stage: "save" }); if (name !== "ofdgoExportMedia") postMessage({ id, type: "export", stage: "save" });