Created
April 5, 2021 22:37
-
-
Save Ethan-Arrowood/1c79aee396eb6c2f1e78380b69ae0f0a to your computer and use it in GitHub Desktop.
Rough perf benchmark for map vs array based header class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const { types } = require('util') | |
const { validateHeaderName, validateHeaderValue } = require('http') | |
const kHeaders = Symbol('headers') | |
function normalizeAndValidateHeaderName (name) { | |
const normalizedHeaderName = name.toLowerCase() | |
validateHeaderName(normalizedHeaderName) | |
return normalizedHeaderName | |
} | |
function normalizeAndValidateHeaderValue (name, value) { | |
const normalizedHeaderName = normalizeAndValidateHeaderName(name) | |
const normalizedHeaderValue = value.replace(/^[\n\t\r\x20]+|[\n\t\r\x20]+$/g, '') | |
validateHeaderValue(normalizedHeaderName, normalizedHeaderValue) | |
return [normalizedHeaderName, normalizedHeaderValue] | |
} | |
function fill (headers, object) { | |
if (Array.isArray(object)) { | |
if (Array.isArray(object[0])) { | |
for (let i = 0, header = object[0]; i < object.length; i++, header = object[i]) { | |
if (header.length !== 2) throw TypeError('header entry must be of length two') | |
headers.append(header[0], header[1]) | |
} | |
} else if (typeof object[0] === 'string') { | |
if (object.length % 2 !== 0) throw TypeError('flattened header init must have even length') | |
for (let i = 0; i < object.length; i += 2) { | |
headers.append(object[i], object[i + 1]) | |
} | |
} else { | |
throw TypeError('invalid array-based header init') | |
} | |
} else if (kHeaders in object) { | |
headers[kHeaders] = new Array(...object[kHeaders]) | |
} else if (!types.isBoxedPrimitive(object)) { | |
for (const [name, value] of Object.entries(object)) { | |
headers.append(name, value) | |
} | |
} | |
} | |
class Headers { | |
constructor (init) { | |
this[kHeaders] = [] | |
if (init && typeof init === 'object') { | |
fill(this, init) | |
} | |
} | |
append (name, value) { | |
const [normalizedName, normalizedValue] = normalizeAndValidateHeaderValue(name, value) | |
let index = this[kHeaders].length | |
for (let i = 0; i < this[kHeaders].length; i += 2) { | |
if (normalizedName === this[kHeaders][i]) { | |
this[kHeaders][i + 1] += `, ${normalizedValue}` | |
return | |
} else if (this[kHeaders][i] > normalizedName) { | |
index = i | |
break | |
} | |
} | |
this[kHeaders].splice(index, 0, normalizedName, normalizedValue) | |
} | |
delete (name) { | |
const normalizedName = normalizeAndValidateHeaderName(name) | |
for (let i = 0; i < this[kHeaders].length; i += 2) { | |
if (normalizedName === this[kHeaders][i]) { | |
this[kHeaders].splice(i, 2) | |
break | |
} | |
} | |
} | |
get (name) { | |
const normalizedName = normalizeAndValidateHeaderName(name) | |
for (let i = 0; i < this[kHeaders].length; i += 2) { | |
if (normalizedName === this[kHeaders][i]) { | |
return this[kHeaders][i + 1] | |
} | |
} | |
return null | |
} | |
has (name) { | |
const normalizedName = normalizeAndValidateHeaderName(name) | |
for (let i = 0; i < this[kHeaders].length; i += 2) { | |
if (normalizedName === this[kHeaders][i]) { | |
return true | |
} | |
} | |
return false | |
} | |
set (name, value) { | |
const [normalizedName, normalizedValue] = normalizeAndValidateHeaderValue(name, value) | |
let index = this[kHeaders].length | |
for (let i = 0; i < this[kHeaders].length; i += 2) { | |
if (normalizedName === this[kHeaders][i] || this[kHeaders][i] > normalizedName) { | |
index = i | |
break | |
} | |
} | |
this[kHeaders].splice(index, 0, normalizedName, normalizedValue) | |
} | |
* keys () { | |
for (const header of this) { | |
yield header[0] | |
} | |
} | |
* values () { | |
for (const header of this) { | |
yield header[1] | |
} | |
} | |
* entries () { | |
yield * this | |
} | |
forEach (callback, thisArg) { | |
for (let i = 0; i < this[kHeaders].length; i += 2) { | |
callback.call(thisArg, this[kHeaders][i + 1], this[kHeaders][i], this) | |
} | |
} | |
* [Symbol.iterator] () { | |
for (let i = 0; i < this[kHeaders].length; i += 2) { | |
yield [this[kHeaders][i], this[kHeaders][i + 1]] | |
} | |
} | |
} | |
module.exports = Headers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const { types } = require('util') | |
const { validateHeaderName, validateHeaderValue } = require('http') | |
const kHeaders = Symbol('headers') | |
function normalizeAndValidateHeaderName (name) { | |
const normalizedHeaderName = name.toLowerCase() | |
validateHeaderName(normalizedHeaderName) | |
return normalizedHeaderName | |
} | |
function normalizeAndValidateHeaderValue (name, value) { | |
const normalizedHeaderName = normalizeAndValidateHeaderName(name) | |
const normalizedHeaderValue = value.replace(/^[\n\t\r\x20]+|[\n\t\r\x20]+$/g, '') | |
validateHeaderValue(normalizedHeaderName, normalizedHeaderValue) | |
return [normalizedHeaderName, normalizedHeaderValue] | |
} | |
function fill (headers, object) { | |
if (Array.isArray(object)) { | |
for (let i = 0, header = object[0]; i < object.length; i++, header = object[i]) { | |
if (header.length !== 2) throw TypeError('header entry must be of length two') | |
headers.append(header[0], header[1]) | |
} | |
} else if (kHeaders in object) { | |
headers[kHeaders] = new Map(object[kHeaders]) | |
} else if (!types.isBoxedPrimitive(object)) { | |
for (const [name, value] of Object.entries(object)) { | |
headers.append(name, value) | |
} | |
} | |
} | |
class Headers { | |
constructor (init) { | |
this[kHeaders] = new Map() | |
if (init && typeof init === 'object') { | |
fill(this, init) | |
} | |
} | |
append (name, value) { | |
const [normalizedHeaderName, normalizedHeaderValue] = normalizeAndValidateHeaderValue(name, value) | |
const existing = this[kHeaders].get(normalizedHeaderName) | |
const newHeaderValue = existing ? existing + ', ' + normalizedHeaderValue : normalizedHeaderValue | |
this[kHeaders].set(normalizedHeaderName, newHeaderValue) | |
} | |
delete (name) { | |
const normalizedHeaderName = normalizeAndValidateHeaderName(name) | |
this[kHeaders].delete(normalizedHeaderName) | |
} | |
get (name) { | |
const normalizedHeaderName = normalizeAndValidateHeaderName(name) | |
const value = this[kHeaders].get(normalizedHeaderName) | |
return value === undefined ? null : value | |
} | |
has (name) { | |
const normalizedHeaderName = normalizeAndValidateHeaderName(name) | |
return this[kHeaders].has(normalizedHeaderName) | |
} | |
set (name, value) { | |
const [normalizedHeaderName, normalizedHeaderValue] = normalizeAndValidateHeaderValue(name, value) | |
this[kHeaders].set(normalizedHeaderName, normalizedHeaderValue) | |
} | |
* keys () { | |
yield * Array.from(this[kHeaders].keys()).sort() | |
} | |
* values () { | |
for (const header of this.entries()) { | |
yield header[1] | |
} | |
} | |
* entries () { | |
yield * Array.from(this[kHeaders].entries()).sort() | |
} | |
forEach (callback, thisArg) { | |
for (const [key, value] of this) { | |
callback.call(thisArg, value, key, this) | |
} | |
} | |
[Symbol.iterator] () { | |
return this.entries() | |
} | |
} | |
module.exports = Headers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const { performance } = require('perf_hooks') | |
const MapHeaders = require('./map') | |
const ArrayHeaders = require('./array') | |
const alphabet = 'abcdefghijklmnopqrstuvwxyz' | |
const _alphabet = alphabet.split('') | |
{ | |
console.log('write performance') | |
function write(headers) { | |
const s = performance.now() | |
for (const letter of _alphabet) { | |
headers.append(letter, letter) | |
} | |
const e = performance.now() | |
return e-s | |
} | |
console.log(`Map: ${write(new MapHeaders())}ms`) | |
console.log(`Array: ${write(new ArrayHeaders())}ms`) | |
} | |
{ | |
console.log('read performance') | |
function read(headers) { | |
for (const letter of _alphabet) { | |
headers.append(letter, letter) | |
} | |
const s = performance.now() | |
let i = 0 | |
for (const header of headers) { | |
i++ | |
} | |
const e = performance.now() | |
return e-s | |
} | |
console.log(`Map: ${read(new MapHeaders())}ms`) | |
console.log(`Array: ${read(new ArrayHeaders())}ms`) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment