Skip to content

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

Merged
mscdex merged 5 commits into
nodejs:masterfrom
mscdex:http-common-perf-checkhttptoken
Jun 14, 2016
Merged

http: optimize checkIsHttpToken() and checkInvalidHeaderChar()#6570
mscdex merged 5 commits into
nodejs:masterfrom
mscdex:http-common-perf-checkhttptoken

Conversation

@mscdex

@mscdexmscdex commented May 4, 2016

Copy link
Copy Markdown
Contributor
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%

@mscdexmscdex 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
mscdexforce-pushed the http-common-perf-checkhttptoken branch from 4961e40 to c7329a3CompareMay 4, 2016 15:46
@mscdex

Copy link
Copy Markdown
ContributorAuthor

@Fishrock123

Copy link
Copy Markdown
Contributor

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

@Fishrock123

Fishrock123 commented May 4, 2016

Copy link
Copy Markdown
Contributor

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

@jasnell

Copy link
Copy Markdown
Member

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

@mscdex

Copy link
Copy Markdown
ContributorAuthor

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
mscdexforce-pushed the http-common-perf-checkhttptoken branch from c7329a3 to bb208d9CompareMay 4, 2016 15:57
@mscdex

Copy link
Copy Markdown
ContributorAuthor

Comment added.

Comment threadlib/_http_common.js Outdated

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed.

@Fishrock123

Copy link
Copy Markdown
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
mscdexforce-pushed the http-common-perf-checkhttptoken branch from bb208d9 to 1d7b821CompareMay 4, 2016 16:52
@jasnell

Copy link
Copy Markdown
Member

Thanks @mscdex. Still LGTM.

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

Copy link
Copy Markdown
ContributorAuthor

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

@mscdex
mscdexforce-pushed the http-common-perf-checkhttptoken branch from 1d7b821 to ac4c6d0CompareMay 7, 2016 21:22
@mscdexmscdex changed the title http: allow checkIsHttpToken to be inlinablehttp: optimize checkIsHttpToken() and checkInvalidHeaderChar()May 7, 2016
@mscdex

mscdex commented May 7, 2016

Copy link
Copy Markdown
ContributorAuthor

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

@mscdexmscdex removed the wip Issues and PRs that are still a work in progress. label May 7, 2016
@mscdex
mscdexforce-pushed the http-common-perf-checkhttptoken branch from ac4c6d0 to 9044655CompareMay 7, 2016 21:48
@mscdex

Copy link
Copy Markdown
ContributorAuthor

Comment threadlib/_http_common.js Outdated

@ChALkeRChALkeRMay 8, 2016

Copy link
Copy Markdown
Member

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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Member

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

@jasnell

Copy link
Copy Markdown
Member

@mscdex ... very nice.

Comment threadlib/_http_common.js Outdated

Copy link
Copy Markdown
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?

@mscdexmscdexMay 8, 2016

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown
ContributorAuthor

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
mscdexforce-pushed the http-common-perf-checkhttptoken branch from 9044655 to 3feadd8CompareMay 8, 2016 20:35
mscdex added 3 commits June 14, 2016 15:13
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
mscdexforce-pushed the http-common-perf-checkhttptoken branch from ed7d020 to c570182CompareJune 14, 2016 19:13
@mscdex
mscdex merged commit c570182 into nodejs:masterJun 14, 2016
@mscdex
mscdex deleted the http-common-perf-checkhttptoken branch June 14, 2016 19:14
@AndreasMadsen

Copy link
Copy Markdown
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
Copy Markdown
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>
@evanlucasevanlucas mentioned this pull request Jun 16, 2016
@MylesBorins

Copy link
Copy Markdown
Contributor

@mscdex lts?

@mscdex

mscdex commented Jul 11, 2016

Copy link
Copy Markdown
ContributorAuthor

@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
Copy Markdown
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
Copy Markdown
Contributor

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

@MylesBorins

Copy link
Copy Markdown
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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

httpIssues or PRs related to the http subsystem.performanceIssues and PRs related to the performance of Node.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mscdex@Fishrock123@jasnell@ChALkeR@bnoordhuis@AndreasMadsen@MylesBorins