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

http: optimize checkIsHttpToken() and checkInvalidHeaderChar() #6570

Merged
merged 5 commits into from
Jun 14, 2016

Conversation

mscdex
Copy link
Contributor

@mscdex mscdex commented May 4, 2016

Checklist
  • tests and code linting passes
  • the commit message follows commit guidelines
Affected core subsystem(s)
  • http
Description of change

This PR optimizes checkIsHttpToken() by:

  • Reducing the source code size to allow it to be inlinable with default v8 settings
  • Tweaking the order of the conditionals a bit to optimize for common cases.
  • Moving the character checking logic to a separate inlinable function to allow the main function to permit some loop unrolling.

checkInvalidHeaderChar() is also optimized by moving the character checking logic to a separate inlinable function to allow the main function to permit some loop unrolling.

The results from the existing check_is_http_token benchmark are (with the iterations bumped up to 5e8):

http/check_is_http_token.js key=TCN n=500000000: ./node: 80661000 ./node-master: 76596000 .................... 5.31%
http/check_is_http_token.js key=ETag n=500000000: ./node: 68376000 ./node-master: 59987000 .................. 13.98%
http/check_is_http_token.js key=date n=500000000: ./node: 77423000 ./node-master: 59011000 .................. 31.20%
http/check_is_http_token.js key=Vary n=500000000: ./node: 71596000 ./node-master: 59517000 .................. 20.30%
http/check_is_http_token.js key=server n=500000000: ./node: 86029000 ./node-master: 41288000 ............... 108.36%
http/check_is_http_token.js key=Server n=500000000: ./node: 82373000 ./node-master: 43516000 ................ 89.29%
http/check_is_http_token.js key=status n=500000000: ./node: 86021000 ./node-master: 43569000 ................ 97.43%
http/check_is_http_token.js key=version n=500000000: ./node: 66875000 ./node-master: 38341000 ............... 74.42%
http/check_is_http_token.js key=Expires n=500000000: ./node: 68358000 ./node-master: 36367000 ............... 87.97%
http/check_is_http_token.js key=alt-svc n=500000000: ./node: 60369000 ./node-master: 35545000 ............... 69.84%
http/check_is_http_token.js key=location n=500000000: ./node: 54662000 ./node-master: 34359000 .............. 59.09%
http/check_is_http_token.js key=Connection n=500000000: ./node: 41642000 ./node-master: 25207000 ............ 65.20%
http/check_is_http_token.js key=Keep-Alive n=500000000: ./node: 36692000 ./node-master: 27106000 ............ 35.36%
http/check_is_http_token.js key=content-type n=500000000: ./node: 29480000 ./node-master: 20660000 .......... 42.69%
http/check_is_http_token.js key=Content-Type n=500000000: ./node: 28407000 ./node-master: 22059000 .......... 28.77%
http/check_is_http_token.js key=Cache-Control n=500000000: ./node: 26861000 ./node-master: 19907000 ......... 34.93%
http/check_is_http_token.js key=Last-Modified n=500000000: ./node: 25668000 ./node-master: 19906000 ......... 28.95%
http/check_is_http_token.js key=Accept-Ranges n=500000000: ./node: 26276000 ./node-master: 18999000 ......... 38.30%
http/check_is_http_token.js key=content-length n=500000000: ./node: 22521000 ./node-master: 18320000 ........ 22.93%
http/check_is_http_token.js key=x-frame-options n=500000000: ./node: 19494000 ./node-master: 16385000 ....... 18.97%
http/check_is_http_token.js key=x-xss-protection n=500000000: ./node: 18531000 ./node-master: 14178000 ...... 30.71%
http/check_is_http_token.js key=Content-Encoding n=500000000: ./node: 21053000 ./node-master: 15840000 ...... 32.91%
http/check_is_http_token.js key=Content-Location n=500000000: ./node: 21052000 ./node-master: 15485000 ...... 35.95%
http/check_is_http_token.js key=Transfer-Encoding n=500000000: ./node: 19648000 ./node-master: 14687000 ..... 33.78%
http/check_is_http_token.js key=alternate-protocol n=500000000: ./node: 16859000 ./node-master: 13820000 .... 21.98%
http/check_is_http_token.js key=: n=500000000: ./node: 201910000 ./node-master: 105210000 ................... 91.92%
http/check_is_http_token.js key=@@ n=500000000: ./node: 201910000 ./node-master: 104490000 .................. 93.23%
http/check_is_http_token.js key=中文呢 n=500000000: ./node: 185510000 ./node-master: 101830000 ............... 82.18%
http/check_is_http_token.js key=((((()))) n=500000000: ./node: 316610000 ./node-master: 103700000 .......... 205.32%
http/check_is_http_token.js key=:alternate-protocol n=500000000: ./node: 202710000 ./node-master: 103840000 . 95.21%
http/check_is_http_token.js key=alternate-protocol: n=500000000: ./node: 16225000 ./node-master: 12317000 ... 31.73%

The results from the new check_invalid_header_char benchmark are:

http/check_invalid_header_char.js key="" n="500000000": ./node: 589640000 ./node-master: 605710000 ............................................................................................................ -2.65%
http/check_invalid_header_char.js key="1" n="500000000": ./node: 159060000 ./node-master: 156190000 ............................................................................................................ 1.83%
http/check_invalid_header_char.js key="\t\t\t\t\t\t\t\t\t\tFoo bar baz" n="500000000": ./node: 14964000 ./node-master: 11644000 ............................................................................... 28.51%
http/check_invalid_header_char.js key="keep-alive" n="500000000": ./node: 41767000 ./node-master: 24943000 .................................................................................................... 67.45%
http/check_invalid_header_char.js key="close" n="500000000": ./node: 108590000 ./node-master: 54515000 ........................................................................................................ 99.18%
http/check_invalid_header_char.js key="gzip" n="500000000": ./node: 83603000 ./node-master: 66625000 .......................................................................................................... 25.48%
http/check_invalid_header_char.js key="20091" n="500000000": ./node: 58258000 ./node-master: 56457000 .......................................................................................................... 3.19%
http/check_invalid_header_char.js key="private" n="500000000": ./node: 68037000 ./node-master: 39502000 ....................................................................................................... 72.24%
http/check_invalid_header_char.js key="text/html; charset=utf-8" n="500000000": ./node: 12537000 ./node-master: 10960000 ...................................................................................... 14.39%
http/check_invalid_header_char.js key="text/plain" n="500000000": ./node: 41925000 ./node-master: 24940000 .................................................................................................... 68.10%
http/check_invalid_header_char.js key="Sat, 07 May 2016 16:54:48 GMT" n="500000000": ./node: 10075000 ./node-master: 9243500 ................................................................................... 9.00%
http/check_invalid_header_char.js key="SAMEORIGIN" n="500000000": ./node: 40381000 ./node-master: 24941000 .................................................................................................... 61.91%
http/check_invalid_header_char.js key="en-US" n="500000000": ./node: 110410000 ./node-master: 54398000 ....................................................................................................... 102.97%
http/check_invalid_header_char.js key="Here is a value that is really a folded header value\r\n  this should be      supported, but it is not currently" n="500000000": ./node: 5479200 ./node-master: 5359200 . 2.24%
http/check_invalid_header_char.js key="中文呢" n="500000000": ./node: 533970000 ./node-master: 280280000 ....................................................................................................... 90.51%
http/check_invalid_header_char.js key="foo\nbar" n="500000000": ./node: 182810000 ./node-master: 70721000 .................................................................................................... 158.49%
http/check_invalid_header_char.js key="�" n="500000000": ./node: 468570000 ./node-master: 294120000 ........................................................................................................... 59.31%

@mscdex mscdex added http Issues or PRs related to the http subsystem. performance Issues and PRs related to the performance of Node.js. labels May 4, 2016
@mscdex mscdex force-pushed the http-common-perf-checkhttptoken branch from 4961e40 to c7329a3 Compare May 4, 2016 15:46
@mscdex
Copy link
Contributor Author

mscdex commented May 4, 2016

@Fishrock123
Copy link
Contributor

Is this something we should do any sort of linting for so that we maintain it? Or add a comment?

@Fishrock123
Copy link
Contributor

Fishrock123 commented May 4, 2016

Aside: has anyone looked into if these v8 settings would be better tweaked by node by default?

@jasnell
Copy link
Member

jasnell commented May 4, 2016

Code comments would be good (outside of the function ;-) ...)
Otherwise LGTM

@mscdex
Copy link
Contributor Author

mscdex commented May 4, 2016

I can add a comment I suppose. I'm not sure if there is an inline eslint comment we can add to enforce function code size or not.

As far as changing the default max_inlined_source_size, I think I trust v8 to use a reasonable default value here. If you increase it too much, you could actually end up with lower performing code (and larger compiled code size).

@mscdex mscdex force-pushed the http-common-perf-checkhttptoken branch from c7329a3 to bb208d9 Compare May 4, 2016 15:57
@mscdex
Copy link
Contributor Author

mscdex commented May 4, 2016

Comment added.

var ch = val.charCodeAt(i);

const ch = val.charCodeAt(i);
if (ch >= 94 && ch <= 122) // a-z
Copy link
Member

Choose a reason for hiding this comment

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

94-122 is not a-z, it's ^_`a-z

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@Fishrock123
Copy link
Contributor

I'm not sure if there is an inline eslint comment we can add to enforce function code size or not.

I think you can do /* eslint-enable: ... */ then /* eslint-disable: ... */ ... I'll try to look into this, it can probably detect function size with a custom plugin.

@mscdex mscdex force-pushed the http-common-perf-checkhttptoken branch from bb208d9 to 1d7b821 Compare May 4, 2016 16:52
@jasnell
Copy link
Member

jasnell commented May 6, 2016

Thanks @mscdex. Still LGTM.

@mscdex mscdex added the wip Issues and PRs that are still a work in progress. label May 7, 2016
@mscdex
Copy link
Contributor Author

mscdex commented May 7, 2016

I think I may have found something that improves performance quite a bit more for every case, so do not merge yet.

@mscdex mscdex force-pushed the http-common-perf-checkhttptoken branch from 1d7b821 to ac4c6d0 Compare May 7, 2016 21:22
@mscdex mscdex changed the title http: allow checkIsHttpToken to be inlinable http: optimize checkIsHttpToken() and checkInvalidHeaderChar() May 7, 2016
@mscdex
Copy link
Contributor Author

mscdex commented May 7, 2016

Ok, I've pushed new changes. In addition to the dramatically better performance of checkIsHttpToken(), I have also significantly increased the performance of checkInvalidHeaderChar().

@mscdex mscdex removed the wip Issues and PRs that are still a work in progress. label May 7, 2016
@mscdex mscdex force-pushed the http-common-perf-checkhttptoken branch from ac4c6d0 to 9044655 Compare May 7, 2016 21:48
@mscdex
Copy link
Contributor Author

mscdex commented May 7, 2016

return false;
if (ch >= 33 && ch <= 46)
return true;
if (ch === 124 || ch === 126)
Copy link
Member

@ChALkeR ChALkeR May 8, 2016

Choose a reason for hiding this comment

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

Ok, now that this is split in two methods, are there still reasons to omit the comment here? This is // | ~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've now added the valid character list in the comment preceding the function instead of documenting them inline.

@ChALkeR
Copy link
Member

ChALkeR commented May 8, 2016

@mscdex Perhaps renaming ch to c in isValidTokenChar would help to keep the list of chars in comments?

@jasnell
Copy link
Member

jasnell commented May 8, 2016

@mscdex ... very nice.

return false;
continue;
if (len > 3) {
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this the common case? Can you quantify how much the manual loop unrolling helps?

Copy link
Contributor Author

@mscdex mscdex May 8, 2016

Choose a reason for hiding this comment

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

I didn't save the old results, but there was a lesser performance improvement with the pure loop implementation. I can run it again without and post the results here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok here are the results with just the loop, no unrolling:

http/check_is_http_token.js key="TCN" n="500000000": ./node: 73576000 ./node-master: 76945000 ................... -4.38%
http/check_is_http_token.js key="ETag" n="500000000": ./node: 60694000 ./node-master: 59833000 ................... 1.44%
http/check_is_http_token.js key="date" n="500000000": ./node: 71346000 ./node-master: 59617000 .................. 19.68%
http/check_is_http_token.js key="Vary" n="500000000": ./node: 64590000 ./node-master: 58538000 .................. 10.34%
http/check_is_http_token.js key="server" n="500000000": ./node: 49815000 ./node-master: 43208000 ................ 15.29%
http/check_is_http_token.js key="Server" n="500000000": ./node: 44101000 ./node-master: 42926000 ................. 2.74%
http/check_is_http_token.js key="status" n="500000000": ./node: 49805000 ./node-master: 43655000 ................ 14.09%
http/check_is_http_token.js key="version" n="500000000": ./node: 43245000 ./node-master: 37774000 ............... 14.48%
http/check_is_http_token.js key="Expires" n="500000000": ./node: 39172000 ./node-master: 37568000 ................ 4.27%
http/check_is_http_token.js key="alt-svc" n="500000000": ./node: 38508000 ./node-master: 35600000 ................ 8.17%
http/check_is_http_token.js key="location" n="500000000": ./node: 38132000 ./node-master: 34186000 .............. 11.54%
http/check_is_http_token.js key="Connection" n="500000000": ./node: 26488000 ./node-master: 24878000 ............. 6.47%
http/check_is_http_token.js key="Keep-Alive" n="500000000": ./node: 29028000 ./node-master: 27224000 ............. 6.63%
http/check_is_http_token.js key="content-type" n="500000000": ./node: 23468000 ./node-master: 21554000 ........... 8.88%
http/check_is_http_token.js key="Content-Type" n="500000000": ./node: 24507000 ./node-master: 23320000 ........... 5.09%
http/check_is_http_token.js key="Cache-Control" n="500000000": ./node: 22685000 ./node-master: 19883000 ......... 14.09%
http/check_is_http_token.js key="Last-Modified" n="500000000": ./node: 22605000 ./node-master: 19911000 ......... 13.53%
http/check_is_http_token.js key="Accept-Ranges" n="500000000": ./node: 22516000 ./node-master: 19637000 ......... 14.66%
http/check_is_http_token.js key="content-length" n="500000000": ./node: 20171000 ./node-master: 18336000 ........ 10.01%
http/check_is_http_token.js key="x-frame-options" n="500000000": ./node: 18221000 ./node-master: 16510000 ....... 10.36%
http/check_is_http_token.js key="x-xss-protection" n="500000000": ./node: 16391000 ./node-master: 14117000 ...... 16.11%
http/check_is_http_token.js key="Content-Encoding" n="500000000": ./node: 18711000 ./node-master: 16602000 ...... 12.70%
http/check_is_http_token.js key="Content-Location" n="500000000": ./node: 18612000 ./node-master: 16531000 ...... 12.59%
http/check_is_http_token.js key="Transfer-Encoding" n="500000000": ./node: 17713000 ./node-master: 15734000 ..... 12.58%
http/check_is_http_token.js key="alternate-protocol" n="500000000": ./node: 14988000 ./node-master: 14720000 ..... 1.82%
http/check_is_http_token.js key=":" n="500000000": ./node: 155200000 ./node-master: 108860000 ................... 42.57%
http/check_is_http_token.js key="@@" n="500000000": ./node: 151200000 ./node-master: 109090000 .................. 38.60%
http/check_is_http_token.js key="中文呢" n="500000000": ./node: 138090000 ./node-master: 101780000 ............... 35.67%
http/check_is_http_token.js key="((((())))" n="500000000": ./node: 184100000 ./node-master: 105320000 ........... 74.80%
http/check_is_http_token.js key=":alternate-protocol" n="500000000": ./node: 141000000 ./node-master: 103870000 . 35.75%
http/check_is_http_token.js key="alternate-protocol:" n="500000000": ./node: 14917000 ./node-master: 11983000 ... 24.49%

@mscdex mscdex force-pushed the http-common-perf-checkhttptoken branch from 9044655 to 3feadd8 Compare May 8, 2016 20:35
PR-URL: nodejs#6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
PR-URL: nodejs#6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
PR-URL: nodejs#6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
@mscdex mscdex force-pushed the http-common-perf-checkhttptoken branch from ed7d020 to c570182 Compare June 14, 2016 19:13
@mscdex mscdex merged commit c570182 into nodejs:master Jun 14, 2016
@mscdex mscdex deleted the http-common-perf-checkhttptoken branch June 14, 2016 19:14
@AndreasMadsen
Copy link
Member

@mscdex not converting benchmark arguments to integers breaks some benchmarks. For example after applying c570182 ./node benchmark/common.js http chunked will fail with:

http/chunked.js
buffer.js:150
    throw err;
    ^

TypeError: "size" argument must be a number
    at Function.Buffer.alloc (buffer.js:159:3)
    at Benchmark.main [as fn] (/Users/Andreas/GitHub/node/benchmark/http/chunked.js:21:22)
    at Benchmark._run (/Users/Andreas/GitHub/node/benchmark/common.js:139:17)
    at /Users/Andreas/GitHub/node/benchmark/common.js:96:10
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)
    at Module.runMain (module.js:577:11)
    at run (node.js:340:7)
    at startup (node.js:132:9)
    at node.js:455:3
child process exited with code 1

@AndreasMadsen
Copy link
Member

@mscdex not calling process.exit(0) after bench.report() will break some benchmarks. For example after applying 83432bf ./node benchmark/common.js dgram array-vs-concat will stall after the first configurations.

dgram/array-vs-concat.js
dgram/array-vs-concat.js len="64" num="100" chunks="1" type="concat" dur="5": 0.06249
^C -- it never completes

In my bechmark refactor #7094 I have added process.exit(0) to all the benchmark that requires it, see db93461

evanlucas pushed a commit that referenced this pull request Jun 16, 2016
This commit both makes checkIsHttpToken() inlinable and extracts
the character checking logic to a separate inlinable function so that
the main loop can be unrolled a bit.

PR-URL: #6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
evanlucas pushed a commit that referenced this pull request Jun 16, 2016
This commit optimizes checkInvalidHeaderChar() by unrolling the
character checking loop a bit.

Additionally, some changes to the benchmark runner are needed in
order for the included benchmark to be run correctly. Specifically,
the regexp used to parse `key=value` parameters contained a greedy
quantifier that was causing the `key` to match part of the `value`
if `value` contained an equals sign.

PR-URL: #6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
evanlucas pushed a commit that referenced this pull request Jun 16, 2016
PR-URL: #6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
evanlucas pushed a commit that referenced this pull request Jun 16, 2016
PR-URL: #6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
evanlucas pushed a commit that referenced this pull request Jun 16, 2016
PR-URL: #6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
@evanlucas evanlucas mentioned this pull request Jun 16, 2016
@MylesBorins
Copy link
Contributor

@mscdex lts?

@mscdex
Copy link
Contributor Author

mscdex commented Jul 11, 2016

@thealphanerd Possibly, but #7311 is a related followup PR that would need to be landed and backported at the same time, since it fixes existing benchmarks due to the benchmark changes included in this PR.

@MylesBorins
Copy link
Contributor

@mscdex I'll hold off then. Please tag the other as lts-watch and consider sending them together as a backport

@MylesBorins
Copy link
Contributor

@mscdex thoughts on this now? Would you be up for submitting a backport

@MylesBorins
Copy link
Contributor

@mscdex I'm going to opt not to land this on v4.x. Please feel free to submit a backport if you feel it should land

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
http Issues or PRs related to the http subsystem. performance Issues and PRs related to the performance of Node.js.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants