]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
perf(server-renderer): optimize `unrollBuffer` by avoiding promises (#11340)
authorVlad <negezor@gmail.com>
Fri, 12 Jul 2024 14:39:21 +0000 (01:39 +1100)
committerGitHub <noreply@github.com>
Fri, 12 Jul 2024 14:39:21 +0000 (22:39 +0800)
packages/server-renderer/__tests__/unrollBuffer.bench.ts [new file with mode: 0644]
packages/server-renderer/src/renderToString.ts

diff --git a/packages/server-renderer/__tests__/unrollBuffer.bench.ts b/packages/server-renderer/__tests__/unrollBuffer.bench.ts
new file mode 100644 (file)
index 0000000..b5e03ce
--- /dev/null
@@ -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<typeof createBuffer>, 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<typeof createBuffer>, 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)
+      },
+    },
+  )
+})
index 0e9299ee834e1567b644689399536446bcc6aecb..b931a4d55b872993f3a3e92e463a276da163cfa0 100644 (file)
@@ -11,26 +11,46 @@ import { type SSRBuffer, type SSRContext, renderComponentVNode } from './render'
 
 const { isVNode } = ssrUtils
 
-async function unrollBuffer(buffer: SSRBuffer): Promise<string> {
-  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> | 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> | string {
+  return nestedUnrollBuffer(buffer, '', 0)
 }
 
 function unrollBufferSync(buffer: SSRBuffer): string {