From 625bd3905047173390eeaffd0ffcbb0bf340bb52 Mon Sep 17 00:00:00 2001 From: Marcos Casagrande Date: Mon, 14 Aug 2023 21:13:55 +0200 Subject: [PATCH] perf(ext/headers): optimize headers iterable (#20155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR makes more optimizations to headers iterable by removing `ObjectEntries` which was consistently prominent in the flame graph when benchmarking an express server. **this PR** ``` cpu: 13th Gen Intel(R) Core(TM) i9-13900H runtime: deno 1.36.1 (x86_64-unknown-linux-gnu) benchmark time (avg) iter/s (min … max) p75 p99 p995 ------------------------------------------------------------------ ----------------------------- headers iter 9.6 µs/iter 104,134.1 (8.74 µs … 131.31 µs) 9.47 µs 12.61 µs 17.81 µs ``` **main** ``` cpu: 13th Gen Intel(R) Core(TM) i9-13900H runtime: deno 1.36.1 (x86_64-unknown-linux-gnu) benchmark time (avg) iter/s (min … max) p75 p99 p995 ------------------------------------------------------------------ ----------------------------- headers iter 12.87 µs/iter 77,675.9 (11.97 µs … 132.34 µs) 12.76 µs 16.49 µs 26.4 µs ``` ```js const headers = new Headers({ "Content-Type": "application/json", "X-Content-Type": "application/json", "Date": "Thu, 14 Aug 2023 17:45:10 GMT", "X-Deno": "Deno", "Powered-By": "Deno", "Content-Encoding": "gzip", "Set-Cookie": "__Secure-ID=123; Secure; Domain=example.com", "Content-Length": "150", "Vary": "Accept-Encoding, Accept, X-Requested-With", }); Deno.bench('headers iter', () => { [...headers] }) ``` --- ext/fetch/20_headers.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ext/fetch/20_headers.js b/ext/fetch/20_headers.js index 929eaf816a..4e1729e01c 100644 --- a/ext/fetch/20_headers.js +++ b/ext/fetch/20_headers.js @@ -26,7 +26,6 @@ const { ArrayPrototypeSort, ArrayPrototypeJoin, ArrayPrototypeSplice, - ObjectEntries, ObjectHasOwn, RegExpPrototypeTest, Symbol, @@ -238,8 +237,8 @@ class Headers { // The order of steps are not similar to the ones suggested by the // spec but produce the same result. - const headers = {}; - const cookies = []; + const seenHeaders = {}; + const entries = []; for (let i = 0; i < list.length; ++i) { const entry = list[i]; const name = byteLowerCase(entry[0]); @@ -250,27 +249,25 @@ class Headers { // so must be given to the user as multiple headers. // The else block of the if statement is spec compliant again. if (name === "set-cookie") { - ArrayPrototypePush(cookies, [name, value]); + ArrayPrototypePush(entries, [name, value]); } else { // The following code has the same behaviour as getHeader() // at the end of loop. But it avoids looping through the entire // list to combine multiple values with same header name. It // instead gradually combines them as they are found. - let header = headers[name]; - if (header && header.length > 0) { - header += "\x2C\x20" + value; + const seenHeaderIndex = seenHeaders[name]; + if (seenHeaderIndex !== undefined) { + const entryValue = entries[seenHeaderIndex][1]; + entries[seenHeaderIndex][1] = entryValue.length > 0 + ? entryValue + "\x2C\x20" + value + : value; } else { - header = value; + seenHeaders[name] = entries.length; // store header index in entries array + ArrayPrototypePush(entries, [name, value]); } - headers[name] = header; } } - const entries = ObjectEntries(headers); - for (let i = 0; i < cookies.length; ++i) { - ArrayPrototypePush(entries, cookies[i]); - } - ArrayPrototypeSort( entries, (a, b) => {