-
Notifications
You must be signed in to change notification settings - Fork 524
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(util/parseHeaders): If the header name is buffer (#2501)
* initial implementation * test: fix * compatible API * fix: tree * add benchmark * fix: lint * fix: benchmark * perf * use number key * remove unsafe * format & add comment * fix: benchmark import path * better benchmark * better benchmark * perf: rewrite tree * test: fuzz test * fix test * test * test: remove tree * refactor * refactor * suggested change * test: refactor * add use strict * test: refactor * add type comment * check length * test: perf * improve type * fix: type
- Loading branch information
Showing
7 changed files
with
303 additions
and
11 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { bench, group, run } from 'mitata' | ||
import { parseHeaders } from '../lib/core/util.js' | ||
|
||
const target = [ | ||
{ | ||
'Content-Type': 'application/json', | ||
Date: 'Wed, 01 Nov 2023 00:00:00 GMT', | ||
'Powered-By': 'NodeJS', | ||
'Content-Encoding': 'gzip', | ||
'Set-Cookie': '__Secure-ID=123; Secure; Domain=example.com', | ||
'Content-Length': '150', | ||
Vary: 'Accept-Encoding, Accept, X-Requested-With' | ||
}, | ||
{ | ||
'Content-Type': 'text/html; charset=UTF-8', | ||
'Content-Length': '1234', | ||
Date: 'Wed, 06 Dec 2023 12:47:57 GMT', | ||
Server: 'Bing' | ||
}, | ||
{ | ||
'Content-Type': 'image/jpeg', | ||
'Content-Length': '56789', | ||
Date: 'Wed, 06 Dec 2023 12:48:12 GMT', | ||
Server: 'Bing', | ||
ETag: '"a1b2c3d4e5f6g7h8i9j0"' | ||
}, | ||
{ | ||
Cookie: 'session_id=1234567890abcdef', | ||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', | ||
Host: 'www.bing.com', | ||
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', | ||
'Accept-Language': 'en-US,en;q=0.5', | ||
'Accept-Encoding': 'gzip, deflate, br' | ||
}, | ||
{ | ||
Location: 'https://www.bing.com/search?q=bing', | ||
Status: '302 Found', | ||
Date: 'Wed, 06 Dec 2023 12:48:27 GMT', | ||
Server: 'Bing', | ||
'Content-Type': 'text/html; charset=UTF-8', | ||
'Content-Length': '0' | ||
}, | ||
{ | ||
'Content-Type': | ||
'multipart/form-data; boundary=----WebKitFormBoundary1234567890', | ||
'Content-Length': '98765', | ||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', | ||
Host: 'www.bing.com', | ||
Accept: '*/*', | ||
'Accept-Language': 'en-US,en;q=0.5', | ||
'Accept-Encoding': 'gzip, deflate, br' | ||
}, | ||
{ | ||
'Content-Type': 'application/json; charset=UTF-8', | ||
'Content-Length': '2345', | ||
Date: 'Wed, 06 Dec 2023 12:48:42 GMT', | ||
Server: 'Bing', | ||
Status: '200 OK', | ||
'Cache-Control': 'no-cache, no-store, must-revalidate' | ||
}, | ||
{ | ||
Host: 'www.example.com', | ||
Connection: 'keep-alive', | ||
Accept: 'text/html, application/xhtml+xml, application/xml;q=0.9,;q=0.8', | ||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' | ||
} | ||
] | ||
|
||
const headers = Array.from(target, (x) => | ||
Object.entries(x) | ||
.flat() | ||
.map((c) => Buffer.from(c)) | ||
) | ||
|
||
const headersIrregular = Array.from( | ||
target, | ||
(x) => Object.entries(x) | ||
.flat() | ||
.map((c) => Buffer.from(c.toUpperCase())) | ||
) | ||
|
||
// avoid JIT bias | ||
bench('noop', () => {}) | ||
bench('noop', () => {}) | ||
bench('noop', () => {}) | ||
bench('noop', () => {}) | ||
bench('noop', () => {}) | ||
bench('noop', () => {}) | ||
|
||
group('parseHeaders', () => { | ||
bench('parseHeaders', () => { | ||
for (let i = 0; i < headers.length; ++i) { | ||
parseHeaders(headers[i]) | ||
} | ||
}) | ||
bench('parseHeaders (irregular)', () => { | ||
for (let i = 0; i < headersIrregular.length; ++i) { | ||
parseHeaders(headersIrregular[i]) | ||
} | ||
}) | ||
}) | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 7000)) | ||
|
||
await run() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
'use strict' | ||
|
||
/** @type {Record<string, string | undefined>} */ | ||
const headerNameLowerCasedRecord = {} | ||
|
||
|
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
'use strict' | ||
|
||
const { wellknownHeaderNames } = require('./constants') | ||
|
||
class TstNode { | ||
/** @type {any} */ | ||
value = null | ||
/** @type {null | TstNode} */ | ||
left = null | ||
/** @type {null | TstNode} */ | ||
middle = null | ||
/** @type {null | TstNode} */ | ||
right = null | ||
/** @type {number} */ | ||
code | ||
/** | ||
* @param {Uint8Array} key | ||
* @param {any} value | ||
*/ | ||
constructor (key, value) { | ||
if (key.length === 0) { | ||
throw new TypeError('Unreachable') | ||
} | ||
this.code = key[0] | ||
if (key.length > 1) { | ||
this.middle = new TstNode(key.subarray(1), value) | ||
} else { | ||
this.value = value | ||
} | ||
} | ||
|
||
/** | ||
* @param {Uint8Array} key | ||
* @param {any} value | ||
*/ | ||
add (key, value) { | ||
if (key.length === 0) { | ||
throw new TypeError('Unreachable') | ||
} | ||
const code = key[0] | ||
if (this.code === code) { | ||
if (key.length === 1) { | ||
this.value = value | ||
} else if (this.middle !== null) { | ||
this.middle.add(key.subarray(1), value) | ||
} else { | ||
this.middle = new TstNode(key.subarray(1), value) | ||
} | ||
} else if (this.code < code) { | ||
if (this.left !== null) { | ||
this.left.add(key, value) | ||
} else { | ||
this.left = new TstNode(key, value) | ||
} | ||
} else { | ||
if (this.right !== null) { | ||
this.right.add(key, value) | ||
} else { | ||
this.right = new TstNode(key, value) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param {Uint8Array} key | ||
* @return {TstNode | null} | ||
*/ | ||
search (key) { | ||
const keylength = key.length | ||
let index = 0 | ||
let node = this | ||
while (node !== null && index < keylength) { | ||
let code = key[index] | ||
// A-Z | ||
if (code >= 0x41 && code <= 0x5a) { | ||
// Lowercase for uppercase. | ||
code |= 32 | ||
} | ||
while (node !== null) { | ||
if (code === node.code) { | ||
if (keylength === ++index) { | ||
// Returns Node since it is the last key. | ||
return node | ||
} | ||
node = node.middle | ||
break | ||
} | ||
node = node.code < code ? node.left : node.right | ||
} | ||
} | ||
return null | ||
} | ||
} | ||
|
||
class TernarySearchTree { | ||
/** @type {TstNode | null} */ | ||
node = null | ||
|
||
/** | ||
* @param {Uint8Array} key | ||
* @param {any} value | ||
* */ | ||
insert (key, value) { | ||
if (this.node === null) { | ||
this.node = new TstNode(key, value) | ||
} else { | ||
this.node.add(key, value) | ||
} | ||
} | ||
|
||
/** | ||
* @param {Uint8Array} key | ||
*/ | ||
lookup (key) { | ||
return this.node?.search(key)?.value ?? null | ||
} | ||
} | ||
|
||
const tree = new TernarySearchTree() | ||
|
||
for (let i = 0; i < wellknownHeaderNames.length; ++i) { | ||
const key = wellknownHeaderNames[i].toLowerCase() | ||
tree.insert(Buffer.from(key), key) | ||
} | ||
|
||
module.exports = { | ||
TernarySearchTree, | ||
tree | ||
} |
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
'use strict' | ||
|
||
const { TernarySearchTree } = require('../lib/core/tree') | ||
const { test } = require('tap') | ||
|
||
test('Ternary Search Tree', (t) => { | ||
t.plan(1) | ||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' | ||
const charactersLength = characters.length | ||
|
||
function generateAsciiString (length) { | ||
let result = '' | ||
for (let i = 0; i < length; ++i) { | ||
result += characters[Math.floor(Math.random() * charactersLength)] | ||
} | ||
return result | ||
} | ||
const tst = new TernarySearchTree() | ||
|
||
const LENGTH = 5000 | ||
|
||
/** @type {string[]} */ | ||
const random = new Array(LENGTH) | ||
/** @type {Buffer[]} */ | ||
const randomBuffer = new Array(LENGTH) | ||
|
||
for (let i = 0; i < LENGTH; ++i) { | ||
const key = generateAsciiString((Math.random() * 100 + 5) | 0) | ||
const lowerCasedKey = random[i] = key.toLowerCase() | ||
randomBuffer[i] = Buffer.from(key) | ||
tst.insert(Buffer.from(lowerCasedKey), lowerCasedKey) | ||
} | ||
|
||
t.test('all', (t) => { | ||
t.plan(LENGTH) | ||
for (let i = 0; i < LENGTH; ++i) { | ||
t.equal(tst.lookup(randomBuffer[i]), random[i]) | ||
} | ||
}) | ||
}) |
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