undici
undici copied to clipboard
perf: Improve FormData
Benchmark
Benchmark Script
import { bench, group, run } from 'mitata'
import { FormData } from '../lib/fetch/formdata.js'
import symbols from '../lib/fetch/symbols.js'
const kState = symbols.kState
const data = [
['a', 'a'],
['b', 'b'],
['c', 'c'],
['d', 'd'],
['e', 'e'],
['f', 'f'],
['g', 'g'],
['h', 'h'],
['i', 'i'],
['j', 'j']
]
// avoid JIT bias
bench('noop', () => {})
bench('noop', () => {})
bench('noop', () => {})
bench('noop', () => {})
bench('noop', () => {})
bench('noop', () => {})
group('not allow duplicate', () => {
const fb = new FormData()
for (let i = 0; i < data.length; ++i) {
fb.append(data[i][0], data[i][1])
}
const state = fb[kState]
function getNewFromData () {
const fb = new FormData()
fb[kState] = state.slice(0)
return fb
}
bench('FromData#get', () => {
const fb = getNewFromData()
for (let i = 0; i < data.length; ++i) {
fb.get(data[i][0])
}
return null
})
bench('FromData#getAll', () => {
const fb = getNewFromData()
for (let i = 0; i < data.length; ++i) {
fb.getAll(data[i][0])
}
return null
})
bench('FormData#set', () => {
const fb = getNewFromData()
for (let i = 0; i < data.length; ++i) {
fb.set(data[i][0], data[i][1])
}
return null
})
bench('FormData#has', () => {
const fb = getNewFromData()
for (let i = 0; i < data.length; ++i) {
fb.has(data[i][0])
}
return null
})
bench('FormData#delete', () => {
const fb = getNewFromData()
for (let i = 0; i < data.length; ++i) {
fb.delete(data[i][0])
}
return null
})
bench('FormData#append', () => {
const fb = getNewFromData()
for (let i = 0; i < data.length; ++i) {
fb.append(data[i][0], data[i][1])
}
return null
})
bench('FormData#forEach', () => {
const h = []
getNewFromData().forEach((v, k) => h.push([k, v]))
return h
})
bench('FormData.{Symbol.iterator}', () => {
const h = []
for (const [k, v] of getNewFromData()) h.push([k, v])
return h
})
})
group('allow duplicate', () => {
const dataAllowDuplicate = [...data, ...data]
const fbAllowDuplicate = new FormData()
for (let i = 0; i < dataAllowDuplicate.length; ++i) {
fbAllowDuplicate.append(dataAllowDuplicate[i][0], dataAllowDuplicate[i][1])
}
const stateAllowDuplicate = fbAllowDuplicate[kState]
function getNewFromDataAllowDuplicate () {
const fb = new FormData()
fb[kState] = stateAllowDuplicate.slice(0)
return fb
}
bench('FromData#get', () => {
const fb = getNewFromDataAllowDuplicate()
for (let i = 0; i < data.length; ++i) {
fb.get(data[i][0])
}
return null
})
bench('FromData#getAll', () => {
const fb = getNewFromDataAllowDuplicate()
for (let i = 0; i < data.length; ++i) {
fb.getAll(data[i][0])
}
return null
})
bench('FormData#set', () => {
const fb = getNewFromDataAllowDuplicate()
for (let i = 0; i < data.length; ++i) {
fb.set(data[i][0], data[i][1])
}
return null
})
bench('FormData#has', () => {
const fb = getNewFromDataAllowDuplicate()
for (let i = 0; i < data.length; ++i) {
fb.has(data[i][0])
}
return null
})
bench('FormData#delete', () => {
const fb = getNewFromDataAllowDuplicate()
for (let i = 0; i < data.length; ++i) {
fb.delete(data[i][0])
}
return null
})
bench('FormData#append', () => {
const fb = getNewFromDataAllowDuplicate()
for (let i = 0; i < data.length; ++i) {
fb.append(data[i][0], data[i][1])
}
return null
})
bench('FormData#forEach', () => {
const h = []
getNewFromDataAllowDuplicate().forEach((v, k) => h.push([k, v]))
return h
})
bench('FormData.{Symbol.iterator}', () => {
const h = []
for (const [k, v] of getNewFromDataAllowDuplicate()) h.push([k, v])
return h
})
})
await new Promise((resolve) => setTimeout(resolve, 7000))
await run()
- main
• not allow duplicate
------------------------------------------------------------------ -----------------------------
FromData#get 326.78 ns/iter (290.95 ns … 888.79 ns) 337.46 ns 528.4 ns 888.79 ns
FromData#getAll 686.06 ns/iter (617.63 ns … 1.3 µs) 681.08 ns 1.3 µs 1.3 µs
FormData#set 9.77 µs/iter (7.7 µs … 8.69 ms) 8.6 µs 35.3 µs 51.8 µs
FormData#has 312.19 ns/iter (277.24 ns … 748.52 ns) 309.8 ns 634.96 ns 748.52 ns
FormData#delete 568.33 ns/iter (516.23 ns … 1.22 µs) 552.4 ns 1.22 µs 1.22 µs
FormData#append 8.79 µs/iter (5.7 µs … 30.23 ms) 7.5 µs 18.3 µs 22.6 µs
FormData#forEach 7.02 µs/iter (3.6 µs … 3.69 ms) 5.2 µs 19.6 µs 23.3 µs
FormData.{Symbol.iterator} 6.68 µs/iter (3.5 µs … 2.94 ms) 5.1 µs 14.7 µs 19 µs
• allow duplicate
------------------------------------------------------------------ -----------------------------
FromData#get 338.52 ns/iter (290.42 ns … 1.69 µs) 322.69 ns 758.92 ns 1.69 µs
FromData#getAll 858.92 ns/iter (780.13 ns … 1.39 µs) 824.32 ns 1.39 µs 1.39 µs
FormData#set 9.94 µs/iter (8.5 µs … 486.9 µs) 9.1 µs 24.7 µs 31.6 µs
FormData#has 303.27 ns/iter (279.27 ns … 554.31 ns) 303.61 ns 513.63 ns 554.31 ns
FormData#delete 773.45 ns/iter (708.99 ns … 1.16 µs) 768.31 ns 1.16 µs 1.16 µs
FormData#append 6.17 µs/iter (5.86 µs … 7.96 µs) 6.24 µs 7.96 µs 7.96 µs
FormData#forEach 11.19 µs/iter (6.7 µs … 2.66 ms) 9.4 µs 26.2 µs 38.7 µs
FormData.{Symbol.iterator} 11.05 µs/iter (6.4 µs … 4.85 ms) 9.2 µs 25.8 µs 39.9 µs
- this patch
• not allow duplicate
------------------------------------------------------------------ -----------------------------
FromData#get 316.32 ns/iter (277.12 ns … 726.24 ns) 310.59 ns 651.66 ns 726.24 ns
FromData#getAll 633.78 ns/iter (545.28 ns … 1.04 µs) 693.27 ns 1.04 µs 1.04 µs
FormData#set 8.04 µs/iter (6.6 µs … 449.1 µs) 7.7 µs 20.4 µs 26.2 µs
FormData#has 305.56 ns/iter (276.85 ns … 569.68 ns) 305.73 ns 553.85 ns 569.68 ns
FormData#delete 908.17 ns/iter (755.47 ns … 1.36 µs) 976.48 ns 1.36 µs 1.36 µs
FormData#append 10.06 µs/iter (6.1 µs … 17.74 ms) 8.5 µs 21.7 µs 28.3 µs
FormData#forEach 206.91 ns/iter (149.26 ns … 2.2 µs) 200.06 ns 564.31 ns 877.15 ns
FormData.{Symbol.iterator} 8.15 µs/iter (3.4 µs … 26.49 ms) 5.7 µs 17.4 µs 22.4 µs
• allow duplicate
------------------------------------------------------------------ -----------------------------
FromData#get 369 ns/iter (300 ns … 4.84 ms) 400 ns 700 ns 800 ns
FromData#getAll 832.91 ns/iter (739.33 ns … 1.4 µs) 852.74 ns 1.4 µs 1.4 µs
FormData#set 8.69 µs/iter (7.5 µs … 671.8 µs) 8 µs 21.6 µs 27.3 µs
FormData#has 313.64 ns/iter (278.46 ns … 652.42 ns) 320.79 ns 635.95 ns 652.42 ns
FormData#delete 1.58 µs/iter (1.39 µs … 2.18 µs) 1.7 µs 2.18 µs 2.18 µs
FormData#append 7.09 µs/iter (6.59 µs … 8.48 µs) 7.21 µs 8.48 µs 8.48 µs
FormData#forEach 291.27 ns/iter (263.77 ns … 601.03 ns) 288.9 ns 599.71 ns 601.03 ns
FormData.{Symbol.iterator} 10.34 µs/iter (6.2 µs … 3.75 ms) 8.6 µs 27.9 µs 43.3 µs
Codecov Report
Attention: 227 lines in your changes are missing coverage. Please review.
Comparison is base (
e39a632) 85.54% compared to head (f07b204) 85.29%. Report is 281 commits behind head on main.
| Files | Patch % | Lines |
|---|---|---|
| lib/fetch/util.js | 60.41% | 57 Missing :warning: |
| lib/fetch/index.js | 69.36% | 53 Missing :warning: |
| lib/handler/RetryHandler.js | 74.35% | 30 Missing :warning: |
| lib/cache/cache.js | 12.12% | 29 Missing :warning: |
| lib/fetch/dataURL.js | 79.31% | 12 Missing :warning: |
| lib/api/readable.js | 83.92% | 9 Missing :warning: |
| lib/core/diagnostics.js | 84.74% | 9 Missing :warning: |
| lib/eventsource/eventsource.js | 96.09% | 5 Missing :warning: |
| lib/core/request.js | 94.36% | 4 Missing :warning: |
| lib/fetch/formdata.js | 90.47% | 4 Missing :warning: |
| ... and 7 more |
Additional details and impacted files
@@ Coverage Diff @@
## main #2560 +/- ##
==========================================
- Coverage 85.54% 85.29% -0.26%
==========================================
Files 76 84 +8
Lines 6858 7624 +766
==========================================
+ Hits 5867 6503 +636
- Misses 991 1121 +130
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
@ronag Benchmarks updated and performance improved. Can you take a look at it?
Yeah agreed. The forEach benchmarks are misleading because the implementation is now wrong (probably, I'd have to test it out). The WPTs must not cover inserting entries while iterating over it.