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

Improve performance of HTTPHeaders.subscript(canonicalForm:) #1952

Merged
merged 1 commit into from
Sep 13, 2021

Conversation

glbrntt
Copy link
Contributor

@glbrntt glbrntt commented Sep 13, 2021

Motivation:

When getting the canonical form of header values any ascii whitespace
is stripped from the values. The current implementation does this by
doing equality checks on Character which is quite expensive.

Modifications:

  • Update the Substring.trimWhitespace() function to trim on a UTF8
    view
  • Add performance tests.

Result:

Retrieving the canonical form of header values is cheaper, significantly
so when values contain whitespace.

On my machine:

  • http_headers_canonical_form: 0.16
  • http_headers_canonical_form_trimming_whitespace (without patch): 0.47
  • http_headers_canonical_form_trimming_whitespace (with patch): 0.24

@glbrntt glbrntt added the semver/patch No public API change. label Sep 13, 2021
@glbrntt glbrntt requested a review from Lukasa September 13, 2021 13:06
Copy link
Contributor

@Davidde94 Davidde94 left a comment

Choose a reason for hiding this comment

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

LGTM.

Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

Thanks for this! I've left a note in the diff.

Can I also ask you to add an allocation counter test for this? Perhaps in a separate PR so we get a nice before-and-after? Re-using either or both of the perf tests would be fine.

Sources/NIOHTTP1/HTTPTypes.swift Show resolved Hide resolved
Comment on lines +546 to +554
case UInt8(ascii: " "),
UInt8(ascii: "\t"),
UInt8(ascii: "\r"),
UInt8(ascii: "\n"):
return true

default:
return false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Apparently two are missing:

  • Vertical tab \v
  • Form feed \f

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm disinclined to make this more complex. We're only really trying to catch the OWS rule from the HTTP -semantics draft, which strictly forbids newline characters, and I don't think we really expect to see them, so arguably we could even remove the \r and \n cases. VTAB and form-feed should also be disregarded by this loop for the same reason.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds fine to me tbh, although it says SHOULD which I'm sure someone somewhere interprets as "not necessary"

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, you can put a VTAB in there: the mistake would be for us to strip it, not for us to fail to. If your VTAB makes it to the app the app is naturally going to flip its lid: what we shouldn't do is treat it as though it was accidentally equivalent to SP or HTAB.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we follow up with a PR to remove \r and \n?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Or perhaps a good first issue)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think follow-up, happy to have you do it.

@glbrntt
Copy link
Contributor Author

glbrntt commented Sep 13, 2021

Can I also ask you to add an allocation counter test for this? Perhaps in a separate PR so we get a nice before-and-after? Re-using either or both of the perf tests would be fine.

Do you expect a difference or is this just for completeness? (I ask because there is no difference)

@Lukasa
Copy link
Contributor

Lukasa commented Sep 13, 2021

Completeness. We should actually add a test case that has a long header field name (more than 15 characters long both before and after the slice), because I had to do a lot of code reading to convince myself that allocating a Substring from a Substring.UTF8View would not allocate new backing storage.

Motivation:

When getting the canonical form of header values any ascii whitespace
is stripped from the values. The current implementation does this by
doing equality checks on `Character` which is quite expensive.

Modifications:

- Update the `Substring.trimWhitespace()` function to trim on a UTF8
  view

Result:

Retrieving the canonical form of header values is cheaper, significantly
so when values contain whitespace.
@glbrntt glbrntt force-pushed the gb-headers-canonical-form-perf branch from e0fb43b to dd59d5c Compare September 13, 2021 15:29
@glbrntt
Copy link
Contributor Author

glbrntt commented Sep 13, 2021

@swift-nio-bot test perf please

@swift-server-bot
Copy link

performance report

build id: 77

timestamp: Mon Sep 13 16:54:05 UTC 2021

results

nameminmaxmeanstd
write_http_headers 0.004175228 0.004204059 0.0041843486 8.496649773750564e-06
http_headers_canonical_form 0.088066669 0.088648648 0.0883149362 0.00024200285511713385
http_headers_canonical_form_trimming_whitespace 0.166675255 0.167411527 0.1671675433 0.00025041346680332034
http_headers_canonical_form_trimming_whitespace_from_short_string 0.152118162 0.158569104 0.1532490596 0.0019270898161849659
http_headers_canonical_form_trimming_whitespace_from_long_string 0.233780961 0.23447633 0.233947494 0.0002329770421584295
bytebuffer_write_12MB_short_string_literals 0.536216842 0.540487154 0.5372444514 0.001245638303050489
bytebuffer_write_12MB_short_calculated_strings 0.536651006 0.538627406 0.5374118569 0.0005423251430652732
bytebuffer_write_12MB_medium_string_literals 0.17697449 0.179316968 0.1779634885 0.0009594945137301595
bytebuffer_write_12MB_medium_calculated_strings 0.229024714 0.232025672 0.23056863789999998 0.0010050183868377137
bytebuffer_write_12MB_large_calculated_strings 0.150138737 0.152460797 0.1507665711 0.0006329726690437148
bytebuffer_lots_of_rw 0.483907709 0.490974026 0.4852673461 0.002058921982370088
bytebuffer_write_http_response_ascii_only_as_string 0.040655404 0.041338095 0.0409420794 0.00025084140404389206
bytebuffer_write_http_response_ascii_only_as_staticstring 0.031833052 0.032415331 0.0319947783 0.00018858982582021724
bytebuffer_write_http_response_some_nonascii_as_string 0.04040764 0.041203895 0.040675823099999994 0.00029070972928275875
bytebuffer_write_http_response_some_nonascii_as_staticstring 0.03171516 0.032332765 0.0318420582 0.0001752392548690691
no-net_http1_10k_reqs_1_conn 0.10869306 0.110996705 0.10985291049999998 0.000680012643758638
http1_10k_reqs_1_conn 0.60097647 0.610555774 0.6061008892 0.0029021748850189036
http1_10k_reqs_100_conns 0.597133852 0.599853171 0.5983384694 0.0007885073616834166
future_whenallsucceed_100k_immediately_succeeded_off_loop 0.076944275 0.078497476 0.07785144029999999 0.0003960260570349832
future_whenallsucceed_100k_immediately_succeeded_on_loop 0.077056804 0.084721774 0.078217605 0.002308651368936033
future_whenallsucceed_100k_deferred_off_loop 0.288957096 0.29287986 0.2909427502 0.0010971015447954718
future_whenallsucceed_100k_deferred_on_loop 0.132997617 0.135882376 0.1338687675 0.0009077912173102425
future_whenallcomplete_100k_immediately_succeeded_off_loop 0.035761052 0.036338119 0.035949693399999996 0.0002115676059803748
future_whenallcomplete_100k_immediately_succeeded_on_loop 0.035731364 0.036606403 0.0360091115 0.00026228620901444126
future_whenallcomplete_100k_deferred_off_loop 0.21253689 0.223491616 0.2172619973 0.003078072991979055
future_whenallcomplete_100k_deferred_on_loop 0.070507583 0.073969083 0.0715337303 0.0009139969555563281
future_reduce_10k_futures 0.039020096 0.039568009 0.0392807098 0.0002149571745988292
future_reduce_into_10k_futures 0.037479105 0.038081381 0.037634962 0.0001906631415239386
channel_pipeline_1m_events 0.097157396 0.097290264 0.0972196562 5.586451266690796e-05
websocket_encode_50b_space_at_front_1m_frames_cow 0.517752192 0.523078356 0.5185171523000001 0.0016123783988960668
websocket_encode_50b_space_at_front_1m_frames_cow_masking 0.066051439 0.066534677 0.06621130210000001 0.00021638238410351588
websocket_encode_1kb_space_at_front_100k_frames_cow 0.054465489 0.054936186 0.054618776599999995 0.00021672917439575766
websocket_encode_50b_no_space_at_front_1m_frames_cow 0.514935627 0.51543822 0.5151868304 0.00020792816780705557
websocket_encode_1kb_no_space_at_front_100k_frames_cow 0.054472355 0.055048243 0.05462967 0.0002309110250247909
websocket_encode_50b_space_at_front_10k_frames 0.006763001 0.006779879 0.0067689446 5.611985649779689e-06
websocket_encode_50b_space_at_front_10k_frames_masking 0.081802062 0.082300964 0.0820108227 0.00023903965586350971
websocket_encode_1kb_space_at_front_1k_frames 0.000782075 0.00079261 0.0007877601 2.899620490493346e-06
websocket_encode_50b_no_space_at_front_10k_frames 0.006754216 0.007088733 0.0068042773 0.00010523129137924915
websocket_encode_1kb_no_space_at_front_1k_frames 0.000728066 0.000737542 0.0007322479 2.8466526326742275e-06
websocket_decode_125b_100k_frames 0.119280957 0.120886248 0.11991782879999999 0.0005660145587742428
websocket_decode_125b_with_a_masking_key_100k_frames 0.123031695 0.123720424 0.1233744537 0.00022518290387152189
websocket_decode_64kb_100k_frames 0.123674735 0.125455118 0.12464705810000001 0.0007486158490006663
websocket_decode_64kb_with_a_masking_key_100k_frames 0.127478864 0.128016598 0.1278090081 0.00020968474201707248
websocket_decode_64kb_+1_100k_frames 0.122547913 0.123090699 0.1228273134 0.00021624764878423953
websocket_decode_64kb_+1_with_a_masking_key_100k_frames 0.126279791 0.128423812 0.1270838122 0.000656864814168844
circular_buffer_into_byte_buffer_1kb 0.041272904 0.041702998 0.0413773038 0.00016933921844090898
circular_buffer_into_byte_buffer_1mb 0.082287565 0.082892744 0.0825088025 0.00023268688375022308
byte_buffer_view_iterator_1mb 0.02048465 0.020918662 0.0205451453 0.00013208722964352032
byte_to_message_decoder_decode_many_small 0.212797561 0.213197574 0.2128756187 0.00012159999276777269
generate_10k_random_request_keys 0.09047111 0.090814436 0.0906435016 0.00010141495187725969

comparison

name current previous winner diff
write_http_headers 0.004175228 0.004160153 previous 0%
http_headers_canonical_form 0.088066669 0.105399777 current -16%
http_headers_canonical_form_trimming_whitespace 0.166675255 0.45974008 current -63%
http_headers_canonical_form_trimming_whitespace_from_short_string 0.152118162 0.294293338 current -48%
http_headers_canonical_form_trimming_whitespace_from_long_string 0.233780961 0.367409353 current -36%
bytebuffer_write_12MB_short_string_literals 0.536216842 0.509389371 previous 5%
bytebuffer_write_12MB_short_calculated_strings 0.536651006 0.509913766 previous 5%
bytebuffer_write_12MB_medium_string_literals 0.17697449 0.174283993 previous 1%
bytebuffer_write_12MB_medium_calculated_strings 0.229024714 0.231201002 current 0%
bytebuffer_write_12MB_large_calculated_strings 0.150138737 0.146019053 previous 2%
bytebuffer_lots_of_rw 0.483907709 0.481434429 previous 0%
bytebuffer_write_http_response_ascii_only_as_string 0.040655404 0.040457204 previous 0%
bytebuffer_write_http_response_ascii_only_as_staticstring 0.031833052 0.030739611 previous 3%
bytebuffer_write_http_response_some_nonascii_as_string 0.04040764 0.040663835 current 0%
bytebuffer_write_http_response_some_nonascii_as_staticstring 0.03171516 0.03100821 previous 2%
no-net_http1_10k_reqs_1_conn 0.10869306 0.11067599 current -1%
http1_10k_reqs_1_conn 0.60097647 0.602115083 current 0%
http1_10k_reqs_100_conns 0.597133852 0.596705763 previous 0%
future_whenallsucceed_100k_immediately_succeeded_off_loop 0.076944275 0.077635754 current 0%
future_whenallsucceed_100k_immediately_succeeded_on_loop 0.077056804 0.078161338 current -1%
future_whenallsucceed_100k_deferred_off_loop 0.288957096 0.297258474 current -2%
future_whenallsucceed_100k_deferred_on_loop 0.132997617 0.13324068 current 0%
future_whenallcomplete_100k_immediately_succeeded_off_loop 0.035761052 0.026911773 previous 32%
future_whenallcomplete_100k_immediately_succeeded_on_loop 0.035731364 0.035856172 current 0%
future_whenallcomplete_100k_deferred_off_loop 0.21253689 0.218635156 current -2%
future_whenallcomplete_100k_deferred_on_loop 0.070507583 0.071694937 current -1%
future_reduce_10k_futures 0.039020096 0.039104234 current 0%
future_reduce_into_10k_futures 0.037479105 0.038220252 current -1%
channel_pipeline_1m_events 0.097157396 0.097155781 previous 0%
websocket_encode_50b_space_at_front_1m_frames_cow 0.517752192 0.52041186 current 0%
websocket_encode_50b_space_at_front_1m_frames_cow_masking 0.066051439 0.065670567 previous 0%
websocket_encode_1kb_space_at_front_100k_frames_cow 0.054465489 0.054640591 current 0%
websocket_encode_50b_no_space_at_front_1m_frames_cow 0.514935627 0.520490801 current -1%
websocket_encode_1kb_no_space_at_front_100k_frames_cow 0.054472355 0.054630029 current 0%
websocket_encode_50b_space_at_front_10k_frames 0.006763001 0.006858044 current -1%
websocket_encode_50b_space_at_front_10k_frames_masking 0.081802062 0.08197985 current 0%
websocket_encode_1kb_space_at_front_1k_frames 0.000782075 0.000800508 current -2%
websocket_encode_50b_no_space_at_front_10k_frames 0.006754216 0.006770921 current 0%
websocket_encode_1kb_no_space_at_front_1k_frames 0.000728066 0.00072636 previous 0%
websocket_decode_125b_100k_frames 0.119280957 0.116114546 previous 2%
websocket_decode_125b_with_a_masking_key_100k_frames 0.123031695 0.11955792 previous 2%
websocket_decode_64kb_100k_frames 0.123674735 0.120290773 previous 2%
websocket_decode_64kb_with_a_masking_key_100k_frames 0.127478864 0.123605384 previous 3%
websocket_decode_64kb_+1_100k_frames 0.122547913 0.120237131 previous 1%
websocket_decode_64kb_+1_with_a_masking_key_100k_frames 0.126279791 0.124094855 previous 1%
circular_buffer_into_byte_buffer_1kb 0.041272904 0.041240847 previous 0%
circular_buffer_into_byte_buffer_1mb 0.082287565 0.082300224 current 0%
byte_buffer_view_iterator_1mb 0.02048465 0.020485816 current 0%
byte_to_message_decoder_decode_many_small 0.212797561 0.195469634 previous 8%
generate_10k_random_request_keys 0.09047111 0.089964712 previous 0%

significant differences found

@glbrntt
Copy link
Contributor Author

glbrntt commented Sep 13, 2021

Relevant bits:

name current previous winner diff
http_headers_canonical_form 0.088066669 0.105399777 current -16%
http_headers_canonical_form_trimming_whitespace 0.166675255 0.45974008 current -63%
http_headers_canonical_form_trimming_whitespace_from_short_string 0.152118162 0.294293338 current -48%
http_headers_canonical_form_trimming_whitespace_from_long_string 0.233780961 0.367409353 current -36%

@glbrntt glbrntt requested a review from Lukasa September 13, 2021 16:58
@Lukasa Lukasa merged commit 10d9790 into apple:main Sep 13, 2021
@glbrntt glbrntt deleted the gb-headers-canonical-form-perf branch September 13, 2021 17:02
glbrntt added a commit to glbrntt/swift-nio that referenced this pull request Sep 13, 2021
Motivation:

Follow up to apple#1952 (comment)

The OWS rule from the HTTP semantics draft only considers 'SP' and
'HTAB' to be whitespace. We also (unnecessarily) consider CR and LF to
be whitespace.

Modifications:

- Remove CR and LF from the characters we consider to be whitespace
- Update tests

Result:

We no longer consider CR or LF to be whitespace when trimming
whitespace when producing the canonical form of header values.
Lukasa pushed a commit that referenced this pull request Sep 14, 2021
…ues (#1954)

Motivation:

Follow up to #1952 (comment)

The OWS rule from the HTTP semantics draft only considers 'SP' and
'HTAB' to be whitespace. We also (unnecessarily) consider CR and LF to
be whitespace.

Modifications:

- Remove CR and LF from the characters we consider to be whitespace
- Update tests

Result:

We no longer consider CR or LF to be whitespace when trimming
whitespace when producing the canonical form of header values.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants