Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(ext/headers): use regex.test instead of .exec #20125

Merged
merged 3 commits into from
Aug 12, 2023

Conversation

marcosc90
Copy link
Contributor

@marcosc90 marcosc90 commented Aug 10, 2023

This PR improves the performance of Headers.get by using Regex.test instead of .exec. Also replaced the Map used for caching with an object which is a bit faster

This patch

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.get           124.71 ns/iter   8,018,687.3 (115.11 ns … 265.66 ns) 126.05 ns 136.12 ns 142.37 ns

1.36.1

cpu: 13th Gen Intel(R) Core(TM) i9-13900H
runtime: deno 1.36.0 (x86_64-unknown-linux-gnu)

benchmark              time (avg)        iter/s             (min … max)       p75       p99      p995
----------------------------------------------------------------------- -----------------------------
Headers.get           218.91 ns/iter   4,568,172.3 (165.37 ns … 264.44 ns) 241.62 ns 260.94 ns 262.67 ns
const headers = new Headers({
  "Content-Type": "application/json",
  "Date": "Thu, 10 Aug 2023 07: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.get", () => {
  headers.get("x-deno");
});

@marcosc90 marcosc90 changed the title perf(ext/headers): use regex.test instead of .exec [WIP] perf(ext/headers): use regex.test instead of .exec Aug 10, 2023
@marcosc90
Copy link
Contributor Author

Correct benchmarks, since I was using an outdated build without the recent perf additions.

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.get     132.15 ns/iter   7,567,412.3 (126.84 ns … 142.25 ns)  133.9 ns 139.93 ns 141.56 ns

@marcosc90 marcosc90 changed the title [WIP] perf(ext/headers): use regex.test instead of .exec perf(ext/headers): use regex.test instead of .exec Aug 10, 2023
Copy link
Contributor

@mmastrac mmastrac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mmastrac
Copy link
Contributor

mmastrac commented Aug 10, 2023

If you want to try out an experiment we never landed, some testing we did a few months back showed that bit-testing is the absolute fastest way to do this

I believe that this is the code we used for the experiment:

// Generate the BITMASK to test for valid HTTP token codepoints
let a = []; for (var i = 0; i < 128; i++) { a[i >> 4] |= (HTTP_TOKEN_CODE_POINT_RE.test(String.fromCharCode(i)) ? 1 << (i & 15) : 0) }
  function bittest(val) {
    const BITMASK = [
        0,     0, 27898,
      1023, 65534, 51199,
      65535, 22527
    ];
    for (var i = 0; i < val.length; i++) {
      const c = val.charCodeAt(i);
      if ((BITMASK[c >> 4] & (c & 15)) == 0) {
        return false;
      }
    }
    return true;
  }

@marcosc90
Copy link
Contributor Author

marcosc90 commented Aug 10, 2023

I'll do a bit of testing with that @mmastrac


Regarding this PR, I noticed that @bartlomieju changed .test with .exec and resulted in a performance gain.

#19364

Someone want to bench this and post results? All my benches result in .test being much faster than .exec

Here are some random regex bench (Dell XPS - Ubuntu 22.04)

test               13.22 ns/iter  75,648,463.0   (12.13 ns … 24.52 ns)  13.37 ns  14.36 ns  14.88 ns
exec               20.99 ns/iter  47,634,069.5   (18.66 ns … 36.09 ns)  23.21 ns  25.81 ns  26.14 ns
test               15.55 ns/iter  64,288,788.5    (12.11 ns … 24.3 ns)   16.8 ns  20.49 ns  20.87 ns
exec               31.51 ns/iter  31,730,926.6   (25.46 ns … 49.49 ns)  33.23 ns   40.6 ns  41.14 ns
test              13.12 ns/iter  76,225,978.8   (11.82 ns … 15.84 ns)  13.29 ns  14.92 ns  14.92 ns
exec               21.63 ns/iter  46,231,304.9   (18.93 ns … 28.77 ns)  23.93 ns  27.27 ns  27.91 ns

@mmastrac
Copy link
Contributor

As @lino-levan said on the other PR

"The V8 engine is a whimsical place, seemingly random changes can have surprising differences in performance."

Are you using tools/build_bench.ts BTW? We built that tool to help w/benchmarking noise from incremental LTO builds.

@marcosc90
Copy link
Contributor Author

I'm going to close for now, and do some further benching when I have some extra time.

@marcosc90 marcosc90 closed this Aug 11, 2023
@bartlomieju
Copy link
Member

@marcosc90 I think we land this PR as is, we can run internal benches tomorrow to see if there's visible improvement.

@marcosc90
Copy link
Contributor Author

@bartlomieju sounds good to me. I'm also going through all regex being used in headers/response to see if they can be optimized a bit.

@marcosc90 marcosc90 reopened this Aug 11, 2023
@bartlomieju
Copy link
Member

@bartlomieju sounds good to me. I'm also going through all regex being used in headers/response to see if they can be optimized a bit.

Great to hear that as these regexes are showing up in the flamegraphs we plot. If you want to take a deeper look into what's going on when benching HTTP server I highly recommend using samply - you can generate a flamegraph easily by doing samply record -r 20000 deno run -A server.js

@mmastrac mmastrac merged commit b34bd64 into denoland:main Aug 12, 2023
25 checks passed
@mmastrac
Copy link
Contributor

Merged this one as is -- thanks for all the perf fixes!

@marcosc90 marcosc90 deleted the perf-headers-name-check branch August 12, 2023 17:53
littledivy pushed a commit to littledivy/deno that referenced this pull request Aug 21, 2023
This PR improves the performance of `Headers.get` by using `Regex.test`
instead of `.exec`. Also replaced the `Map` used for caching with an
object which is a bit faster

**This patch**

```
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.get           124.71 ns/iter   8,018,687.3 (115.11 ns … 265.66 ns) 126.05 ns 136.12 ns 142.37 ns
```

**1.36.1**

```
cpu: 13th Gen Intel(R) Core(TM) i9-13900H
runtime: deno 1.36.0 (x86_64-unknown-linux-gnu)

benchmark              time (avg)        iter/s             (min … max)       p75       p99      p995
----------------------------------------------------------------------- -----------------------------
Headers.get           218.91 ns/iter   4,568,172.3 (165.37 ns … 264.44 ns) 241.62 ns 260.94 ns 262.67 ns
```

```js
const headers = new Headers({
  "Content-Type": "application/json",
  "Date": "Thu, 10 Aug 2023 07: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.get", () => {
  headers.get("x-deno");
});
```
littledivy pushed a commit that referenced this pull request Aug 21, 2023
This PR improves the performance of `Headers.get` by using `Regex.test`
instead of `.exec`. Also replaced the `Map` used for caching with an
object which is a bit faster

**This patch**

```
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.get           124.71 ns/iter   8,018,687.3 (115.11 ns … 265.66 ns) 126.05 ns 136.12 ns 142.37 ns
```

**1.36.1**

```
cpu: 13th Gen Intel(R) Core(TM) i9-13900H
runtime: deno 1.36.0 (x86_64-unknown-linux-gnu)

benchmark              time (avg)        iter/s             (min … max)       p75       p99      p995
----------------------------------------------------------------------- -----------------------------
Headers.get           218.91 ns/iter   4,568,172.3 (165.37 ns … 264.44 ns) 241.62 ns 260.94 ns 262.67 ns
```

```js
const headers = new Headers({
  "Content-Type": "application/json",
  "Date": "Thu, 10 Aug 2023 07: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.get", () => {
  headers.get("x-deno");
});
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants