chore: benchmark reference

This commit is contained in:
Evan You 2025-02-10 10:04:22 +08:00
parent c317a06043
commit 6df8c01cd0
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
5 changed files with 77 additions and 83 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ TODOs.md
dts-build/packages dts-build/packages
*.tsbuildinfo *.tsbuildinfo
*.tgz *.tgz
packages-private/benchmark/reference

View File

@ -1,22 +1,10 @@
<script setup lang="ts"> <script setup>
import { import { shallowRef, triggerRef } from 'vue'
shallowRef,
triggerRef,
type ShallowRef,
// createSelector,
} from 'vue'
import { buildData } from './data' import { buildData } from './data'
import { defer, wrap } from './profiling' import { defer, wrap } from './profiling'
const isVapor = !!import.meta.env.IS_VAPOR const selected = shallowRef()
const rows = shallowRef([])
const selected = shallowRef<number>()
const rows = shallowRef<
{
id: number
label: ShallowRef<string>
}[]
>([])
// Bench Add: https://jsbench.me/45lzxprzmu/1 // Bench Add: https://jsbench.me/45lzxprzmu/1
const add = wrap('add', () => { const add = wrap('add', () => {
@ -24,7 +12,7 @@ const add = wrap('add', () => {
triggerRef(rows) triggerRef(rows)
}) })
const remove = wrap('remove', (id: number) => { const remove = wrap('remove', id => {
rows.value.splice( rows.value.splice(
rows.value.findIndex(d => d.id === id), rows.value.findIndex(d => d.id === id),
1, 1,
@ -32,7 +20,7 @@ const remove = wrap('remove', (id: number) => {
triggerRef(rows) triggerRef(rows)
}) })
const select = wrap('select', (id: number) => { const select = wrap('select', id => {
selected.value = id selected.value = id
}) })
@ -77,13 +65,11 @@ async function bench() {
} }
} }
// const isSelected = createSelector(selected)
const globalThis = window const globalThis = window
</script> </script>
<template> <template>
<h1>Vue.js ({{ isVapor ? 'Vapor' : 'Virtual DOM' }}) Benchmark</h1> <h1>Vue.js (VDOM) Benchmark</h1>
<div style="display: flex; gap: 4px; margin-bottom: 4px"> <div style="display: flex; gap: 4px; margin-bottom: 4px">
<label> <label>
@ -110,31 +96,37 @@ const globalThis = window
> >
<button @click="bench">Benchmark mounting</button> <button @click="bench">Benchmark mounting</button>
<button id="run" @click="run">Create 1,000 rows</button> <button id="run" @click="run">Create 1,000 rows</button>
<button id="runLots" @click="runLots">Create 10,000 rows</button> <button id="runlots" @click="runLots">Create 10,000 rows</button>
<button id="add" @click="add">Append 1,000 rows</button> <button id="add" @click="add">Append 1,000 rows</button>
<button id="update" @click="update">Update every 10th row</button> <button id="update" @click="update">Update every 10th row</button>
<button id="clear" @click="clear">Clear</button> <button id="clear" @click="clear">Clear</button>
<button id="swaprows" @click="swapRows">Swap Rows</button> <button id="swaprows" @click="swapRows">Swap Rows</button>
</div> </div>
<div id="time"></div> <div id="time"></div>
<table> <table class="table table-hover table-striped test-data">
<tbody> <tbody>
<tr <tr
v-for="row of rows" v-for="row of rows"
:key="row.id" :key="row.id"
:class="{ danger: selected === row.id }" :class="selected === row.id ? 'danger' : ''"
> >
<td>{{ row.id }}</td> <td class="col-md-1">{{ row.id }}</td>
<td> <td class="col-md-4">
<a @click="select(row.id)">{{ row.label.value }}</a> <a @click="select(row.id)">{{ row.label.value }}</a>
</td> </td>
<td> <td class="col-md-1">
<button @click="remove(row.id)">x</button> <a @click="remove(row.id)">
<span class="glyphicon glyphicon-remove" aria-hidden="true">x</span>
</a>
</td> </td>
<td class="col-md-6"></td> <td class="col-md-6"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<span
class="preloadicon glyphicon glyphicon-remove"
aria-hidden="true"
></span>
</template> </template>
<style> <style>

View File

@ -1,22 +1,10 @@
<script setup vapor lang="ts"> <script setup vapor>
import { import { shallowRef, triggerRef } from 'vue'
shallowRef,
triggerRef,
type ShallowRef,
// createSelector,
} from 'vue'
import { buildData } from './data' import { buildData } from './data'
import { defer, wrap } from './profiling' import { defer, wrap } from './profiling'
const isVapor = !!import.meta.env.IS_VAPOR const selected = shallowRef()
const rows = shallowRef([])
const selected = shallowRef<number>()
const rows = shallowRef<
{
id: number
label: ShallowRef<string>
}[]
>([])
// Bench Add: https://jsbench.me/45lzxprzmu/1 // Bench Add: https://jsbench.me/45lzxprzmu/1
const add = wrap('add', () => { const add = wrap('add', () => {
@ -24,7 +12,7 @@ const add = wrap('add', () => {
triggerRef(rows) triggerRef(rows)
}) })
const remove = wrap('remove', (id: number) => { const remove = wrap('remove', id => {
rows.value.splice( rows.value.splice(
rows.value.findIndex(d => d.id === id), rows.value.findIndex(d => d.id === id),
1, 1,
@ -32,7 +20,7 @@ const remove = wrap('remove', (id: number) => {
triggerRef(rows) triggerRef(rows)
}) })
const select = wrap('select', (id: number) => { const select = wrap('select', id => {
selected.value = id selected.value = id
}) })
@ -77,13 +65,11 @@ async function bench() {
} }
} }
// const isSelected = createSelector(selected)
const globalThis = window const globalThis = window
</script> </script>
<template> <template>
<h1>Vue.js ({{ isVapor ? 'Vapor' : 'Virtual DOM' }}) Benchmark</h1> <h1>Vue.js (Vapor) Benchmark</h1>
<div style="display: flex; gap: 4px; margin-bottom: 4px"> <div style="display: flex; gap: 4px; margin-bottom: 4px">
<label> <label>
@ -110,31 +96,37 @@ const globalThis = window
> >
<button @click="bench">Benchmark mounting</button> <button @click="bench">Benchmark mounting</button>
<button id="run" @click="run">Create 1,000 rows</button> <button id="run" @click="run">Create 1,000 rows</button>
<button id="runLots" @click="runLots">Create 10,000 rows</button> <button id="runlots" @click="runLots">Create 10,000 rows</button>
<button id="add" @click="add">Append 1,000 rows</button> <button id="add" @click="add">Append 1,000 rows</button>
<button id="update" @click="update">Update every 10th row</button> <button id="update" @click="update">Update every 10th row</button>
<button id="clear" @click="clear">Clear</button> <button id="clear" @click="clear">Clear</button>
<button id="swaprows" @click="swapRows">Swap Rows</button> <button id="swaprows" @click="swapRows">Swap Rows</button>
</div> </div>
<div id="time"></div> <div id="time"></div>
<table> <table class="table table-hover table-striped test-data">
<tbody> <tbody>
<tr <tr
v-for="row of rows" v-for="row of rows"
:key="row.id" :key="row.id"
:class="{ danger: selected === row.id }" :class="selected === row.id ? 'danger' : ''"
> >
<td>{{ row.id }}</td> <td class="col-md-1">{{ row.id }}</td>
<td> <td class="col-md-4">
<a @click="select(row.id)">{{ row.label.value }}</a> <a @click="select(row.id)">{{ row.label.value }}</a>
</td> </td>
<td> <td class="col-md-1">
<button @click="remove(row.id)">x</button> <a @click="remove(row.id)">
<span class="glyphicon glyphicon-remove" aria-hidden="true">x</span>
</a>
</td> </td>
<td class="col-md-6"></td> <td class="col-md-6"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<span
class="preloadicon glyphicon glyphicon-remove"
aria-hidden="true"
></span>
</template> </template>
<style> <style>

View File

@ -24,7 +24,8 @@ const {
count: countStr, count: countStr,
warmupCount: warmupCountStr, warmupCount: warmupCountStr,
noHeadless, noHeadless,
devBuild, noMinify,
reference,
}, },
} = parseArgs({ } = parseArgs({
allowNegative: true, allowNegative: true,
@ -67,9 +68,12 @@ const {
noHeadless: { noHeadless: {
type: 'boolean', type: 'boolean',
}, },
devBuild: { noMinify: {
type: 'boolean', type: 'boolean',
short: 'd', },
reference: {
type: 'boolean',
short: 'r',
}, },
}, },
}) })
@ -79,10 +83,10 @@ const count = +(/** @type {string}*/ (countStr))
const warmupCount = +(/** @type {string}*/ (warmupCountStr)) const warmupCount = +(/** @type {string}*/ (warmupCountStr))
const sha = await getSha(true) const sha = await getSha(true)
if (!skipLib) { if (!skipLib && !reference) {
await buildLib() await buildLib()
} }
if (!skipApp) { if (!skipApp && !reference) {
await rm('client/dist', { recursive: true }).catch(() => {}) await rm('client/dist', { recursive: true }).catch(() => {})
vdom && (await buildApp(false)) vdom && (await buildApp(false))
!noVapor && (await buildApp(true)) !noVapor && (await buildApp(true))
@ -103,13 +107,10 @@ async function buildLib() {
stdio: 'inherit', stdio: 'inherit',
env: { ...process.env, BENCHMARK: 'true' }, env: { ...process.env, BENCHMARK: 'true' },
} }
const buildOptions = devBuild ? '-df' : '-pf'
const [{ ok }, { ok: ok2 }, { ok: ok3 }] = await Promise.all([ const [{ ok }, { ok: ok2 }, { ok: ok3 }] = await Promise.all([
exec( exec(
'pnpm', 'pnpm',
`run --silent build shared compiler-core compiler-dom ${buildOptions} cjs`.split( `run --silent build shared compiler-core compiler-dom -pf cjs`.split(' '),
' ',
),
options, options,
), ),
exec( exec(
@ -121,7 +122,9 @@ async function buildLib() {
), ),
exec( exec(
'pnpm', 'pnpm',
`run --silent build vue ${buildOptions} esm-browser-vapor`.split(' '), `run --silent build shared reactivity runtime-core runtime-dom runtime-vapor vue -f esm-bundler+esm-bundler-runtime`.split(
' ',
),
options, options,
), ),
]) ])
@ -138,15 +141,15 @@ async function buildApp(isVapor) {
colors.blue(`\nBuilding ${isVapor ? 'Vapor' : 'Virtual DOM'} app...\n`), colors.blue(`\nBuilding ${isVapor ? 'Vapor' : 'Virtual DOM'} app...\n`),
) )
if (!devBuild) process.env.NODE_ENV = 'production' process.env.NODE_ENV = 'production'
const CompilerSFC = await import( const CompilerSFC = await import(
'../../packages/compiler-sfc/dist/compiler-sfc.cjs.js' '../../packages/compiler-sfc/dist/compiler-sfc.cjs.js'
) )
const prodSuffix = devBuild ? '.js' : '.prod.js'
const runtimePath = path.resolve( const runtimePath = path.resolve(
import.meta.dirname, import.meta.dirname,
'../../packages/vue/dist/vue.runtime-with-vapor.esm-browser' + prodSuffix, '../../packages/vue/dist/vue.runtime.esm-bundler.js',
) )
const mode = isVapor ? 'vapor' : 'vdom' const mode = isVapor ? 'vapor' : 'vdom'
@ -157,7 +160,7 @@ async function buildApp(isVapor) {
'import.meta.env.IS_VAPOR': String(isVapor), 'import.meta.env.IS_VAPOR': String(isVapor),
}, },
build: { build: {
minify: !devBuild, minify: !noMinify,
outDir: path.resolve('./client/dist', mode), outDir: path.resolve('./client/dist', mode),
rollupOptions: { rollupOptions: {
onwarn(log, handler) { onwarn(log, handler) {
@ -181,8 +184,10 @@ async function buildApp(isVapor) {
} }
function startServer() { function startServer() {
const server = connect().use(sirv('./client/dist')).listen(port) const server = connect()
printPort(port) .use(sirv(reference ? './reference' : './client/dist', { dev: true }))
.listen(port)
printPort()
process.on('SIGTERM', () => server.close()) process.on('SIGTERM', () => server.close())
return server return server
} }
@ -203,18 +208,25 @@ async function benchmark() {
await browser.close() await browser.close()
} }
/**
* @param {boolean} isVapor
*/
function getURL(isVapor) {
return `http://localhost:${port}/${reference ? '' : isVapor ? 'vapor' : 'vdom'}/`
}
/** /**
* *
* @param {import('puppeteer').Browser} browser * @param {import('puppeteer').Browser} browser
* @param {boolean} isVapor * @param {boolean} isVapor
*/ */
async function doBench(browser, isVapor) { async function doBench(browser, isVapor) {
const mode = isVapor ? 'vapor' : 'vdom' const mode = reference ? `reference` : isVapor ? 'vapor' : 'vdom'
console.info('\n\nmode:', mode) console.info('\n\nmode:', mode)
const page = await browser.newPage() const page = await browser.newPage()
page.emulateCPUThrottling(4) page.emulateCPUThrottling(4)
await page.goto(`http://localhost:${port}/${mode}`, { await page.goto(getURL(isVapor), {
waitUntil: 'networkidle0', waitUntil: 'networkidle0',
}) })
@ -256,7 +268,7 @@ async function doBench(browser, isVapor) {
await clickButton('add') // append rows to large table await clickButton('add') // append rows to large table
await withoutRecord(() => clickButton('clear')) await withoutRecord(() => clickButton('clear'))
await clickButton('runLots') // create many rows await clickButton('runlots') // create many rows
await withoutRecord(() => clickButton('clear')) await withoutRecord(() => clickButton('clear'))
// TODO replace all rows // TODO replace all rows
@ -293,7 +305,7 @@ async function doBench(browser, isVapor) {
for (let i = 1; i <= 10; i++) { for (let i = 1; i <= 10; i++) {
await page.click(`tbody > tr:nth-child(2) > td:nth-child(2) > a`) await page.click(`tbody > tr:nth-child(2) > td:nth-child(2) > a`)
await page.waitForSelector(`tbody > tr:nth-child(2).danger`) await page.waitForSelector(`tbody > tr:nth-child(2).danger`)
await page.click(`tbody > tr:nth-child(2) > td:nth-child(3) > button`) await page.click(`tbody > tr:nth-child(2) > td:nth-child(3) > a`)
await wait() await wait()
} }
} }
@ -372,13 +384,10 @@ function round(n) {
return +n.toFixed(2) return +n.toFixed(2)
} }
/** @param {number} port */ function printPort() {
function printPort(port) {
const vaporLink = !noVapor const vaporLink = !noVapor
? `\nVapor: ${colors.blue(`http://localhost:${port}/vapor`)}` ? `\n${reference ? `Reference` : `Vapor`}: ${colors.blue(getURL(true))}`
: ''
const vdomLink = vdom
? `\nvDom: ${colors.blue(`http://localhost:${port}/vdom`)}`
: '' : ''
const vdomLink = vdom ? `\nvDom: ${colors.blue(getURL(false))}` : ''
console.info(`\n\nServer started at`, vaporLink, vdomLink) console.info(`\n\nServer started at`, vaporLink, vdomLink)
} }

View File

@ -5,7 +5,7 @@
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "pnpm start --devBuild --skipBench --vdom", "dev": "pnpm start --noMinify --skipBench --vdom",
"start": "node index.js" "start": "node index.js"
}, },
"dependencies": { "dependencies": {