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

Zero copy between Netty's Http2Headers and Armeria's HttpHeaders #3817

Merged
merged 9 commits into from Oct 5, 2021

Conversation

ikhoon
Copy link
Contributor

@ikhoon ikhoon commented Sep 7, 2021

Motivation:

Netty's HTTP/2 codec decodes HTTP/2 headers to Http2Headers and
Armeria HTTP/2 codec converts Http2Headers into RequestHeaders,
ResponseHeaders and HttpHeaders.
It is inefficient since it copies headers two times.
After walking through Netty's API, I found out it is possible
to set a custom Http2HeadersDecoder that converts a headers block into
HttpHeaders when building Http2ServerConnectionHandler

Modifications:

  • Add ArmeriaHttp2Headers that implements Netty's Http2Headers and
    internally stores the values in Armeria's HttpHeaders.
  • Manually create Http2ConnectionEncoder and Http2ConnectionDecoder
    on both server and client side to customize Http2HeadersDecoder
  • Update ArmeriaHttpUtil to directly convert Http2Headers to
    HttpHeaders without copies.

Result:

  • Base work for HTTP/2 headers optimization.
  • You can now trace HTTP/2 traffic by adjusting the logging level of com.linecorp.armeria.logging.traffic.{server,client}.http2.
    For example:
    <!-- in your logback.xml -->
    <logger name="com.linecorp.armeria.logging.traffic.server.http2" level="TRACE" />
    <logger name="com.linecorp.armeria.logging.traffic.client.http2" level="TRACE" />

Motivation:

Netty's HTTP/2 codec decodes HTTP/2 headers to `Http2Headers` and
Armeria HTTP/2 codec converts `Http2Headers` into `RequestHeaders`,
`ResponseHeaders` and `HttpHeaders`.
It is inefficient since it copies headers two times.
After walking through Netty's API, I found out it is possible
to set a custom `Http2HeadersDecoder` coverts a headers block into
`HttpHeaders` when building `Http2ServerConnectionHandler`

Modifications:

- Add `ArmeriaHttp2Headers` that implements Netty's `Http2Headers` and
  internally stores the values in Armeria's `HttpHeaders`.
- Manually create `Http2ConnectionEncoder` and `Http2ConnectionDecoder`
  on both server and client side to customize `Http2HeadersDecoder`
- Update `ArmeriaHttpUtil` to directly convert `Http2Headers` to
  `HttpHeaders` without copies.

Result:

TBU
@ikhoon ikhoon added this to the 1.12.0 milestone Sep 7, 2021
@codecov
Copy link

codecov bot commented Sep 7, 2021

Codecov Report

Merging #3817 (7eee550) into master (853e616) will increase coverage by 0.02%.
The diff coverage is 63.10%.

❗ Current head 7eee550 differs from pull request most recent head bd77df5. Consider uploading reports for the commit bd77df5 to get more accurate results
Impacted file tree graph

@@             Coverage Diff              @@
##             master    #3817      +/-   ##
============================================
+ Coverage     73.24%   73.26%   +0.02%     
+ Complexity    15082    15052      -30     
============================================
  Files          1326     1318       -8     
  Lines         58003    57745     -258     
  Branches       7356     7330      -26     
============================================
- Hits          42486    42309     -177     
+ Misses        11774    11681      -93     
- Partials       3743     3755      +12     
Impacted Files Coverage Δ
...p/armeria/internal/common/ArmeriaHttp2Headers.java 50.47% <50.47%> (ø)
...linecorp/armeria/common/StringMultimapBuilder.java 56.14% <66.66%> (+16.71%) ⬆️
...ecorp/armeria/internal/common/ArmeriaHttpUtil.java 88.23% <93.54%> (-0.09%) ⬇️
...armeria/client/HttpClientPipelineConfigurator.java 77.19% <100.00%> (+0.84%) ⬆️
...java/com/linecorp/armeria/common/HttpResponse.java 90.47% <100.00%> (-2.79%) ⬇️
...ia/internal/common/ArmeriaHttp2HeadersDecoder.java 100.00% <100.00%> (ø)
...armeria/server/HttpServerPipelineConfigurator.java 80.82% <100.00%> (+0.90%) ⬆️
...ecorp/armeria/server/ServerHttp2ObjectEncoder.java 78.57% <100.00%> (+4.28%) ⬆️
...p/armeria/server/WrappingTransientHttpService.java 80.00% <0.00%> (-20.00%) ⬇️
...rnal/common/GracefulConnectionShutdownHandler.java 68.08% <0.00%> (-6.39%) ⬇️
... and 105 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 853e616...bd77df5. Read the comment docs.

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

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

Looks like the good first step for optimizing HTTP/2 headers. Thanks for looking into this, @ikhoon !

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Left some minor comments.
Nice work!

outputHeaders.add(name, value);
final HttpHeadersBuilder builder = inputHeaders.toBuilder();

for (Entry<AsciiString, AsciiString> disallowed : HTTP_TO_HTTP2_HEADER_DISALLOWED_LIST) {
Copy link
Member

Choose a reason for hiding this comment

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

How about just using a Set for HTTP_TO_HTTP2_HEADER_DISALLOWED_LIST?

Copy link
Member

Choose a reason for hiding this comment

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

Now, I understand that we use this for case-insensitive comparison.
Let's change the name of the map as @trustin suggested. 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CaseInsensitiveMap sounds good.

builder.remove(HttpHeaderNames.CONTENT_LENGTH);
}
if (builder == null) {
builder = inputHeaders.toBuilder();
Copy link
Member

Choose a reason for hiding this comment

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

How about assigning this builder at the first of the method and remove all if (builder == null)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops... I don't remember why I did like this.🤣
Maybe the code might be changed while fixing test cases.

@anuraaga
Copy link
Collaborator

Any benchmark results? I remember trying an approach to zero copy a long time ago but found it to reduce performance and abandoned it. The approach was probably different than this one though don't remember - would be good to confirm the performance.

@ikhoon
Copy link
Contributor Author

ikhoon commented Sep 29, 2021

I ran some benchmarks. Unfortunately, I didn't get any enhancement or regression. 😭
There is still room for performance improvement. I think the approach is not bad and could be a base work for further optimization.

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Thanks!

@trustin trustin merged commit 755dc71 into line:master Oct 5, 2021
@ikhoon ikhoon deleted the zero-copy-headers branch November 3, 2021 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants