Skip to content

Commit

Permalink
feat: add fast benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Aug 5, 2024
1 parent f0405f1 commit e81132f
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 21 deletions.
4 changes: 2 additions & 2 deletions playground/src/bench/App.vue → benchmark/client/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
<script setup lang="ts" vapor>
import { ref, shallowRef } from '@vue/vapor'
import { buildData } from './data'
import { defer, wrap } from './profiling'
Expand Down
File renamed without changes.
12 changes: 12 additions & 0 deletions benchmark/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Vapor Benchmark</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>
4 changes: 4 additions & 0 deletions benchmark/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createVaporApp } from '@vue/vapor'
import App from './App.vue'

createVaporApp(App).mount('#app')
31 changes: 20 additions & 11 deletions playground/src/bench/profiling.ts → benchmark/client/profiling.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
// @ts-expect-error
/* eslint-disable no-console */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-restricted-globals */

declare module globalThis {
let doProfile: boolean
let times: Record<string, number[]>
}

globalThis.doProfile = false
// const defer = nextTick
const ric =
typeof requestIdleCallback === 'undefined' ? setTimeout : requestIdleCallback
export const defer = () => new Promise(r => ric(r))
export const defer = () => new Promise(r => requestIdleCallback(r))

const times: Record<string, number[]> = {}
const times: Record<string, number[]> = (globalThis.times = {})

export const wrap = (
export function wrap(
id: string,
fn: (...args: any[]) => any,
): ((...args: any[]) => Promise<void>) => {
if (import.meta.env.PROD) return fn
): (...args: any[]) => Promise<void> {
return async (...args) => {
const btns = Array.from(
document.querySelectorAll<HTMLButtonElement>('#control button'),
)
const timeEl = document.getElementById('time')!
timeEl.classList.remove('done')
for (const node of btns) {
node.disabled = true
}
const doProfile = (globalThis as any).doProfile

const { doProfile } = globalThis
await defer()

doProfile && console.profile(id)
Expand All @@ -33,6 +40,7 @@ export const wrap = (
Math.floor(prevTimes.length / 2)
]
const mean = prevTimes.reduce((a, b) => a + b, 0) / prevTimes.length

const msg =
`${id}: min: ${Math.min(...prevTimes).toFixed(2)} / ` +
`max: ${Math.max(...prevTimes).toFixed(2)} / ` +
Expand All @@ -43,7 +51,8 @@ export const wrap = (
`over ${prevTimes.length} runs`
doProfile && console.profileEnd(id)
console.log(msg)
document.getElementById('time')!.textContent = msg
timeEl.textContent = msg
timeEl.classList.add('done')

for (const node of btns) {
node.disabled = false
Expand Down
196 changes: 196 additions & 0 deletions benchmark/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/* eslint-disable no-restricted-syntax */

// @ts-check
import path from 'node:path'
import connect from 'connect'
import Vue from '@vitejs/plugin-vue'
import { build } from 'vite'
import { exec } from '../scripts/utils.js'
import sirv from 'sirv'
import { launch } from 'puppeteer'

const PORT = 8193

await buildVapor()
await buildApp()
const server = startServer()
await bench()
server.close()

process.on('SIGTERM', () => {
server.close()
})

async function buildVapor() {
console.info('Building Vapor...')
const options = {
cwd: path.resolve(import.meta.dirname, '..'),
stdio: 'inherit',
}
const [{ ok }, { ok: ok2 }, { ok: ok3 }] = await Promise.all([
exec(
'pnpm',
'run build shared compiler-core compiler-dom compiler-vapor -pf cjs'.split(
' ',
),
options,
),
exec(
'pnpm',
'run build compiler-sfc compiler-ssr -f cjs'.split(' '),
options,
),
exec('pnpm', 'run build vue-vapor -pf esm-browser'.split(' '), options),
])

if (!ok || !ok2 || !ok3) {
console.error('Failed to build')
process.exit(1)
}
}

async function buildApp() {
console.info('Building app...')

process.env.NODE_ENV = 'production'
const CompilerSFC = await import(
'../packages/compiler-sfc/dist/compiler-sfc.cjs.js'
)
const CompilerVapor = await import(
'../packages/compiler-vapor/dist/compiler-vapor.cjs.prod.js'
)

const vaporRuntime = path.resolve(
import.meta.dirname,
'../packages/vue-vapor/dist/vue-vapor.esm-browser.prod.js',
)
await build({
root: './client',
build: {
minify: 'terser',
},
resolve: {
alias: {
'vue/vapor': vaporRuntime,
'@vue/vapor': vaporRuntime,
},
},
plugins: [
Vue({
compiler: CompilerSFC,
template: {
compiler: /** @type {any} */ (CompilerVapor),
},
}),
],
})
}

function startServer() {
const server = connect().use(sirv('./client/dist')).listen(PORT)
console.info(`Server started at http://localhost:${PORT}`)
return server
}

async function bench() {
const disableFeatures = [
'Translate', // avoid translation popups
'PrivacySandboxSettings4', // avoid privacy popup
'IPH_SidePanelGenericMenuFeature', // bookmark popup see https://github.com/krausest/js-framework-benchmark/issues/1688
]

const args = [
'--js-flags=--expose-gc', // needed for gc() function
'--no-default-browser-check',
'--disable-sync',
'--no-first-run',
'--ash-no-nudges',
'--disable-extensions',
`--disable-features=${disableFeatures.join(',')}`,
]

const browser = await launch({
// headless: false,
args,
})
const page = await browser.newPage()
await page.goto(`http://localhost:${PORT}/`, {
waitUntil: 'networkidle0',
})

await forceGC()

const t = performance.now()
for (let i = 0; i < 50; i++) {
await doAction('run')
await doAction('add')
await doAction('update')
await doAction('swaprows')
await doAction('clear')
}
console.info('Total time:', performance.now() - t, 'ms')

const times = await getTimes()
/** @type {Record<string, { mean: string, std: string }>} */
const result = {}
for (const key in times) {
const mean = getMean(times[key])
const std = getStandardDeviation(times[key])
result[key] = { mean: mean.toFixed(2), std: std.toFixed(2) }
}

// eslint-disable-next-line no-console
console.table(result)

await page.close()
await browser.close()

/**
* @param {string} id
*/
async function doAction(id) {
await page.click(`#${id}`)
await page.waitForSelector('.done')
}

function getTimes() {
return page.evaluate(() => /** @type {any} */ (globalThis).times)
}

async function forceGC() {
await page.evaluate(
"window.gc({type:'major',execution:'sync',flavor:'last-resort'})",
)
}
}

/**
* @param {number[]} nums
* @returns {number}
*/
function getMean(nums) {
return (
nums.reduce(
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
(a, b) => a + b,
0,
) / nums.length
)
}

/**
*
* @param {number[]} array
* @returns
*/
function getStandardDeviation(array) {
const n = array.length
const mean = array.reduce((a, b) => a + b) / n
return Math.sqrt(
array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n,
)
}
19 changes: 19 additions & 0 deletions benchmark/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "benchmark",
"version": "0.0.0",
"author": "三咲智子 Kevin Deng <[email protected]>",
"license": "MIT",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@vitejs/plugin-vue": "npm:@vue-vapor/[email protected]",
"connect": "^3.7.0",
"sirv": "^2.0.4",
"vite": "^5.0.12"
},
"devDependencies": {
"@types/connect": "^3.4.38"
}
}
26 changes: 26 additions & 0 deletions benchmark/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["es2022", "dom"],
"allowJs": true,
"moduleDetection": "force",
"module": "preserve",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"types": ["node", "vite/client"],
"strict": true,
"noUnusedLocals": true,
"declaration": true,
"esModuleInterop": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"noEmit": true,
"paths": {
"vue": ["../packages/vue-vapor/src"],
"@vue/vapor": ["../packages/vue-vapor/src"],
"@vue/*": ["../packages/*/src"]
}
},
"include": ["**/*"]
}
4 changes: 1 addition & 3 deletions playground/setup/dev.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// @ts-check
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))
const resolve = (/** @type {string} */ p) =>
path.resolve(dirname, '../../packages', p)
path.resolve(import.meta.dirname, '../../packages', p)

/**
* @param {Object} [env]
Expand Down
12 changes: 11 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e81132f

Please sign in to comment.