feat(playground): add benchmark

This commit is contained in:
三咲智子 Kevin Deng 2024-02-15 06:34:03 +08:00
parent 95b08e8fc6
commit 670109e287
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
4 changed files with 253 additions and 1 deletions

View File

@ -0,0 +1,116 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
import { buildData } from './data'
import { defer, wrap } from './profiling'
const selected = ref<number>()
const rows = shallowRef<
{
id: number
label: string
}[]
>([])
function setRows(update = rows.value.slice()) {
rows.value = update
}
const add = wrap('add', () => {
rows.value = rows.value.concat(buildData(1000))
})
const remove = wrap('remove', (id: number) => {
rows.value.splice(
rows.value.findIndex(d => d.id === id),
1,
)
setRows()
})
const select = wrap('select', (id: number) => {
selected.value = id
})
const run = wrap('run', () => {
setRows(buildData())
selected.value = undefined
})
const update = wrap('update', () => {
const _rows = rows.value
for (let i = 0; i < _rows.length; i += 10) {
_rows[i].label += ' !!!'
}
setRows()
})
const runLots = wrap('runLots', () => {
setRows(buildData(10000))
selected.value = undefined
})
const clear = wrap('clear', () => {
setRows([])
selected.value = undefined
})
const swapRows = wrap('swap', () => {
const _rows = rows.value
if (_rows.length > 998) {
const d1 = _rows[1]
const d998 = _rows[998]
_rows[1] = d998
_rows[998] = d1
setRows()
}
})
async function bench() {
for (let i = 0; i < 30; i++) {
rows.value = []
await defer()
await runLots()
}
}
</script>
<template>
<h1>Vue.js Vapor Benchmark</h1>
<div
id="control"
style="display: flex; flex-direction: column; width: fit-content; gap: 6px"
>
<button @click="bench">Benchmark mounting</button>
<button id="run" @click="run">Create 1,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="update" @click="update">Update every 10th row</button>
<button id="clear" @click="clear">Clear</button>
<button id="swaprows" @click="swapRows">Swap Rows</button>
</div>
<div id="time"></div>
<table>
<tbody>
<tr
v-for="row of rows"
:key="row.id"
:class="{ danger: row.id === selected }"
>
<td>{{ row.id }}</td>
<td>
<a @click="select(row.id)">{{ row.label }}</a>
</td>
<td>
<button @click="remove(row.id)">x</button>
</td>
<td class="col-md-6"></td>
</tr>
</tbody>
</table>
</template>
<style>
.danger {
background-color: red;
}
</style>

View File

@ -0,0 +1,75 @@
let ID = 1
function _random(max: number) {
return Math.round(Math.random() * 1000) % max
}
export function buildData(count = 1000) {
const adjectives = [
'pretty',
'large',
'big',
'small',
'tall',
'short',
'long',
'handsome',
'plain',
'quaint',
'clean',
'elegant',
'easy',
'angry',
'crazy',
'helpful',
'mushy',
'odd',
'unsightly',
'adorable',
'important',
'inexpensive',
'cheap',
'expensive',
'fancy',
]
const colours = [
'red',
'yellow',
'blue',
'green',
'pink',
'brown',
'purple',
'brown',
'white',
'black',
'orange',
]
const nouns = [
'table',
'chair',
'house',
'bbq',
'desk',
'car',
'pony',
'cookie',
'sandwich',
'burger',
'pizza',
'mouse',
'keyboard',
]
const data = []
for (let i = 0; i < count; i++)
data.push({
id: ID++,
label:
adjectives[_random(adjectives.length)] +
' ' +
colours[_random(colours.length)] +
' ' +
nouns[_random(nouns.length)],
})
return data
}

View File

@ -0,0 +1,61 @@
// @ts-expect-error
globalThis.doProfile = false
// const defer = nextTick
const ric =
typeof requestIdleCallback === 'undefined' ? setTimeout : requestIdleCallback
export const defer = () => new Promise(r => ric(r))
const times: Record<string, number[]> = {}
export const wrap = (
id: string,
fn: (...args: any[]) => any,
): ((...args: any[]) => Promise<void>) => {
if (import.meta.env.PROD) return fn
return async (...args) => {
const btns = Array.from(
document.querySelectorAll<HTMLButtonElement>('#control button'),
)
for (const node of btns) {
node.disabled = true
}
const doProfile = (globalThis as any).doProfile
await defer()
doProfile && console.profile(id)
const start = performance.now()
fn(...args)
await defer()
const time = performance.now() - start
doProfile && console.profileEnd(id)
const prevTimes = times[id] || (times[id] = [])
prevTimes.push(time)
const median = prevTimes.slice().sort((a, b) => a - b)[
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)} / ` +
`median: ${median.toFixed(2)}ms / ` +
`mean: ${mean.toFixed(2)}ms / ` +
`time: ${time.toFixed(2)}ms / ` +
`std: ${getStandardDeviation(prevTimes).toFixed(2)} ` +
`: ${getStandardDeviation(prevTimes).toFixed(2)} ` +
`over ${prevTimes.length} runs`
console.log(msg)
document.getElementById('time')!.textContent = msg
for (const node of btns) {
node.disabled = false
}
}
}
function getStandardDeviation(array: number[]) {
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,
)
}

View File

@ -2,7 +2,7 @@ import { render, unmountComponent } from 'vue/vapor'
import { createApp } from 'vue'
import './style.css'
const modules = import.meta.glob<any>('./*.(vue|js)')
const modules = import.meta.glob<any>('./**/*.(vue|js)')
const mod = (modules['.' + location.pathname] || modules['./App.vue'])()
mod.then(({ default: mod }) => {