From: Vlad Date: Fri, 12 Jul 2024 14:39:21 +0000 (+1100) Subject: perf(server-renderer): optimize `unrollBuffer` by avoiding promises (#11340) X-Git-Tag: v3.4.32~29 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=05779a70bd0b567ae458a07636d229bd07c44c4e;p=thirdparty%2Fvuejs%2Fcore.git perf(server-renderer): optimize `unrollBuffer` by avoiding promises (#11340) --- diff --git a/packages/server-renderer/__tests__/unrollBuffer.bench.ts b/packages/server-renderer/__tests__/unrollBuffer.bench.ts new file mode 100644 index 0000000000..b5e03cea60 --- /dev/null +++ b/packages/server-renderer/__tests__/unrollBuffer.bench.ts @@ -0,0 +1,74 @@ +import { bench, describe } from 'vitest' + +import { type SSRBuffer, createBuffer } from '../src/render' +import { unrollBuffer } from '../src/renderToString' + +function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer { + const buffer = createBuffer() + + function addItems(buf: ReturnType, level: number) { + for (let i = 1; i <= levels * itemsPerLevel; i++) { + buf.push(`sync${level}.${i}`) + } + if (level < levels) { + const subBuffer = createBuffer() + addItems(subBuffer, level + 1) + buf.push(subBuffer.getBuffer()) + } + } + + addItems(buffer, 1) + return buffer.getBuffer() +} + +function createMixedBuffer(levels: number, itemsPerLevel: number): SSRBuffer { + const buffer = createBuffer() + + function addItems(buf: ReturnType, level: number) { + for (let i = 1; i <= levels * itemsPerLevel; i++) { + if (i % 3 === 0) { + // @ts-expect-error testing... + buf.push(Promise.resolve(`async${level}.${i}`)) + } else { + buf.push(`sync${level}.${i}`) + } + } + if (level < levels) { + const subBuffer = createBuffer() + addItems(subBuffer, level + 1) + buf.push(subBuffer.getBuffer()) + } + } + + addItems(buffer, 1) + return buffer.getBuffer() +} + +describe('unrollBuffer', () => { + let syncBuffer = createBuffer().getBuffer() + let mixedBuffer = createBuffer().getBuffer() + + bench( + 'sync', + () => { + return unrollBuffer(syncBuffer) as any + }, + { + setup() { + syncBuffer = createSyncBuffer(5, 3) + }, + }, + ) + + bench( + 'mixed', + () => { + return unrollBuffer(mixedBuffer) as any + }, + { + setup() { + mixedBuffer = createMixedBuffer(5, 3) + }, + }, + ) +}) diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 0e9299ee83..b931a4d55b 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -11,26 +11,46 @@ import { type SSRBuffer, type SSRContext, renderComponentVNode } from './render' const { isVNode } = ssrUtils -async function unrollBuffer(buffer: SSRBuffer): Promise { - if (buffer.hasAsync) { - let ret = '' - for (let i = 0; i < buffer.length; i++) { - let item = buffer[i] - if (isPromise(item)) { - item = await item - } - if (isString(item)) { - ret += item - } else { - ret += await unrollBuffer(item) - } +function nestedUnrollBuffer( + buffer: SSRBuffer, + parentRet: string, + startIndex: number, +): Promise | string { + if (!buffer.hasAsync) { + return parentRet + unrollBufferSync(buffer) + } + + let ret = parentRet + for (let i = startIndex; i < buffer.length; i += 1) { + const item = buffer[i] + if (isString(item)) { + ret += item + continue } - return ret - } else { - // sync buffer can be more efficiently unrolled without unnecessary await - // ticks - return unrollBufferSync(buffer) + + if (isPromise(item)) { + return item.then(nestedItem => { + buffer[i] = nestedItem + return nestedUnrollBuffer(buffer, ret, i) + }) + } + + const result = nestedUnrollBuffer(item, ret, 0) + if (isPromise(result)) { + return result.then(nestedItem => { + buffer[i] = nestedItem + return nestedUnrollBuffer(buffer, '', i) + }) + } + + ret = result } + + return ret +} + +export function unrollBuffer(buffer: SSRBuffer): Promise | string { + return nestedUnrollBuffer(buffer, '', 0) } function unrollBufferSync(buffer: SSRBuffer): string {