Open
Description
Currently prop-setting helpers do this:
function setClass(el, value) {
const prev = recordPropMetadata(el, 'class', value)
if (value !== prev && (value || prev)) {
el.className = value
}
}
function recordPropMetadata(el, key, value) {
const metadata = getMetadata(el)[0]
const prev = metadata[key]
if (prev !== value) metadata[key] = value
return prev
}
function getMetadata(el) {
return el.$$metadata || (el.$$metadata = [{}, {}])
}
recordPropMetadata
has noticeable overhead on every call:
- extra index / key access
- extra array / object allocation
Changes Needed
For template
<div>
<div :id="foo" :class="bar"></div>
</div>
Current codegen:
const n1 = /* ... */
_renderEffect(() => _setDOMProp(n1, "id", foo))
_renderEffect(() => _setClass(n1, bar))
Should be changed to (storing prev update values in the local closure):
const n1 = /* ... */
let _id, _cls
_renderEffect(() => _setDOMProp(n1, "id", _id, (_id = foo)))
_renderEffect(() => _setClass(n1, _cls, (_cls = bar)))
And in the relevant helpers, prev
value should come from the argument instead of element metadata.
Benchmark
A simple benchmark that simulate a typical vapor update function:
<script type="module">
import { Bench } from 'https://esm.sh/tinybench'
function recordPropMetadata(el, key, value) {
const metadata = getMetadata(el)[0]
const prev = metadata[key]
if (prev !== value) metadata[key] = value
return prev
}
function getMetadata(el) {
return el.$$metadata || (el.$$metadata = [{}, {}])
}
function setAttr(el, key, value) {
const oldVal = recordPropMetadata(el, key, value)
if (value !== oldVal) {
if (value != null) {
el.setAttribute(key, value)
} else {
el.removeAttribute(key)
}
}
}
function setAttr2(el, key, oldVal, value) {
if (value !== oldVal) {
if (value != null) {
el.setAttribute(key, value)
} else {
el.removeAttribute(key)
}
}
}
function setClass(el, value) {
const prev = recordPropMetadata(el, 'class', value)
if (value !== prev && (value || prev)) {
el.className = value
}
}
function setClass2(el, prev, value) {
if (value !== prev && (value || prev)) {
el.className = value
}
}
const updateOne = (() => {
const n1 = document.createElement('div')
const n2 = document.createElement('div')
const n3 = document.createElement('div')
const n4 = document.createElement('div')
return val => {
setClass(n1, val)
setAttr(n1, 'id', val)
setClass(n2, val)
setAttr(n3, 'id', val)
setClass(n4, val)
}
})()
const updateTwo = (() => {
const n1 = document.createElement('div')
const n2 = document.createElement('div')
const n3 = document.createElement('div')
const n4 = document.createElement('div')
let cls, id, cls2, id2, cls3
return val => {
setClass2(n1, cls, (cls = val))
setAttr2(n1, 'id', id, (id = val))
setClass2(n2, val, (cls2 = val))
setAttr2(n3, 'id', val, (id2 = val))
setClass2(n4, val, (cls3 = val))
}
})()
const bench = new Bench({ name: 'old value handling for set ops', time: 100 })
let i
bench
.add('in closure', () => {
updateTwo(i++ % 5 ? 'foo' : 'bar')
})
.add('in metadata', () => {
updateOne(i++ % 5 ? 'foo' : 'bar')
})
await bench.run()
console.log(bench.name)
const output = JSON.stringify(bench.table(), null, 2)
document.getElementById('output').textContent = output
</script>
<pre id="output"></pre>
Result in Chrome:
[
{
"Task name": "in closure",
"Latency average (ns)": "51.16 ± 6.19%",
"Latency median (ns)": "0.00",
"Throughput average (ops/s)": "19537649 ± 0.00%",
"Throughput median (ops/s)": "19547644",
"Samples": 1956719
},
{
"Task name": "in metadata",
"Latency average (ns)": "79.90 ± 6.20%",
"Latency median (ns)": "0.00",
"Throughput average (ops/s)": "12505888 ± 0.00%",
"Throughput median (ops/s)": "12515880",
"Samples": 1251588
}
]
Metadata
Assignees
Labels
No labels