2013-01-16 05:22:50 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2012 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
|
|
|
package io.netty.handler.codec.http;
|
|
|
|
|
2015-08-13 04:05:37 +02:00
|
|
|
import io.netty.handler.codec.CharSequenceValueConverter;
|
2016-11-21 21:29:44 +01:00
|
|
|
import io.netty.handler.codec.DateFormatter;
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
import io.netty.handler.codec.DefaultHeaders;
|
2015-08-13 04:05:37 +02:00
|
|
|
import io.netty.handler.codec.DefaultHeaders.NameValidator;
|
2015-10-27 21:47:47 +01:00
|
|
|
import io.netty.handler.codec.DefaultHeadersImpl;
|
2015-08-13 04:05:37 +02:00
|
|
|
import io.netty.handler.codec.HeadersUtils;
|
|
|
|
import io.netty.handler.codec.ValueConverter;
|
2015-04-01 01:23:52 +02:00
|
|
|
import io.netty.util.AsciiString;
|
2015-08-13 04:05:37 +02:00
|
|
|
import io.netty.util.ByteProcessor;
|
|
|
|
import io.netty.util.internal.PlatformDependent;
|
2013-11-28 08:15:14 +01:00
|
|
|
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
import java.util.ArrayList;
|
2013-01-16 05:22:50 +01:00
|
|
|
import java.util.Calendar;
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
import java.util.Collections;
|
2013-01-16 05:22:50 +01:00
|
|
|
import java.util.Date;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2015-01-20 01:48:11 +01:00
|
|
|
import java.util.Map.Entry;
|
2013-01-16 05:22:50 +01:00
|
|
|
import java.util.Set;
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
|
2015-08-13 04:05:37 +02:00
|
|
|
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
|
|
|
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
|
2013-01-16 05:22:50 +01:00
|
|
|
|
2014-09-19 01:04:35 +02:00
|
|
|
/**
|
|
|
|
* Default implementation of {@link HttpHeaders}.
|
|
|
|
*/
|
2013-01-16 05:22:50 +01:00
|
|
|
public class DefaultHttpHeaders extends HttpHeaders {
|
2014-09-19 01:04:35 +02:00
|
|
|
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
|
2015-08-13 04:05:37 +02:00
|
|
|
private static final ByteProcessor HEADER_NAME_VALIDATOR = new ByteProcessor() {
|
|
|
|
@Override
|
|
|
|
public boolean process(byte value) throws Exception {
|
2015-10-27 21:47:47 +01:00
|
|
|
validateHeaderNameElement(value);
|
2015-08-13 04:05:37 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
static final NameValidator<CharSequence> HttpNameValidator = new NameValidator<CharSequence>() {
|
|
|
|
@Override
|
|
|
|
public void validateName(CharSequence name) {
|
2016-05-10 18:09:37 +02:00
|
|
|
if (name == null || name.length() == 0) {
|
|
|
|
throw new IllegalArgumentException("empty headers are not allowed [" + name + "]");
|
|
|
|
}
|
2015-08-13 04:05:37 +02:00
|
|
|
if (name instanceof AsciiString) {
|
|
|
|
try {
|
|
|
|
((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR);
|
|
|
|
} catch (Exception e) {
|
|
|
|
PlatformDependent.throwException(e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Go through each character in the name
|
|
|
|
for (int index = 0; index < name.length(); ++index) {
|
2015-10-27 21:47:47 +01:00
|
|
|
validateHeaderNameElement(name.charAt(index));
|
2015-08-13 04:05:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2014-09-19 01:04:35 +02:00
|
|
|
|
2015-10-27 21:47:47 +01:00
|
|
|
private final DefaultHeaders<CharSequence, CharSequence, ?> headers;
|
2013-01-16 05:22:50 +01:00
|
|
|
|
|
|
|
public DefaultHttpHeaders() {
|
2013-11-13 15:05:12 +01:00
|
|
|
this(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public DefaultHttpHeaders(boolean validate) {
|
2015-08-13 04:05:37 +02:00
|
|
|
this(validate, nameValidator(validate));
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2015-08-13 04:05:37 +02:00
|
|
|
protected DefaultHttpHeaders(boolean validate, NameValidator<CharSequence> nameValidator) {
|
2015-10-27 21:47:47 +01:00
|
|
|
this(new DefaultHeadersImpl<CharSequence, CharSequence>(CASE_INSENSITIVE_HASHER,
|
|
|
|
valueConverter(validate),
|
|
|
|
nameValidator));
|
2014-12-12 13:46:54 +01:00
|
|
|
}
|
|
|
|
|
2015-10-27 21:47:47 +01:00
|
|
|
protected DefaultHttpHeaders(DefaultHeaders<CharSequence, CharSequence, ?> headers) {
|
2015-08-13 04:05:37 +02:00
|
|
|
this.headers = headers;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2013-11-28 08:15:14 +01:00
|
|
|
@Override
|
|
|
|
public HttpHeaders add(HttpHeaders headers) {
|
|
|
|
if (headers instanceof DefaultHttpHeaders) {
|
2014-06-05 11:31:04 +02:00
|
|
|
this.headers.add(((DefaultHttpHeaders) headers).headers);
|
2013-11-28 08:15:14 +01:00
|
|
|
return this;
|
|
|
|
} else {
|
|
|
|
return super.add(headers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public HttpHeaders set(HttpHeaders headers) {
|
|
|
|
if (headers instanceof DefaultHttpHeaders) {
|
2014-06-05 11:31:04 +02:00
|
|
|
this.headers.set(((DefaultHttpHeaders) headers).headers);
|
2013-11-28 08:15:14 +01:00
|
|
|
return this;
|
|
|
|
} else {
|
|
|
|
return super.set(headers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders add(String name, Object value) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.addObject(name, value);
|
2014-06-05 11:31:04 +02:00
|
|
|
return this;
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders add(CharSequence name, Object value) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.addObject(name, value);
|
2013-01-16 15:56:51 +01:00
|
|
|
return this;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2013-01-16 15:56:51 +01:00
|
|
|
public HttpHeaders add(String name, Iterable<?> values) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.addObject(name, values);
|
2014-06-05 11:31:04 +02:00
|
|
|
return this;
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.addObject(name, values);
|
2013-01-16 15:56:51 +01:00
|
|
|
return this;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2014-10-31 08:48:28 +01:00
|
|
|
@Override
|
|
|
|
public HttpHeaders addInt(CharSequence name, int value) {
|
|
|
|
headers.addInt(name, value);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
@Override
|
|
|
|
public HttpHeaders addShort(CharSequence name, short value) {
|
|
|
|
headers.addShort(name, value);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders remove(String name) {
|
|
|
|
headers.remove(name);
|
|
|
|
return this;
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders remove(CharSequence name) {
|
|
|
|
headers.remove(name);
|
2013-01-16 15:56:51 +01:00
|
|
|
return this;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders set(String name, Object value) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.setObject(name, value);
|
2014-06-05 11:31:04 +02:00
|
|
|
return this;
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders set(CharSequence name, Object value) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.setObject(name, value);
|
2013-01-16 15:56:51 +01:00
|
|
|
return this;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders set(String name, Iterable<?> values) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.setObject(name, values);
|
2014-06-05 11:31:04 +02:00
|
|
|
return this;
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public HttpHeaders set(CharSequence name, Iterable<?> values) {
|
2014-09-19 01:04:35 +02:00
|
|
|
headers.setObject(name, values);
|
2013-01-16 15:56:51 +01:00
|
|
|
return this;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2014-10-31 08:48:28 +01:00
|
|
|
@Override
|
|
|
|
public HttpHeaders setInt(CharSequence name, int value) {
|
|
|
|
headers.setInt(name, value);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
@Override
|
|
|
|
public HttpHeaders setShort(CharSequence name, short value) {
|
|
|
|
headers.setShort(name, value);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
@Override
|
2013-01-16 15:56:51 +01:00
|
|
|
public HttpHeaders clear() {
|
2014-06-05 11:31:04 +02:00
|
|
|
headers.clear();
|
2013-01-16 15:56:51 +01:00
|
|
|
return this;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public String get(String name) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return get((CharSequence) name);
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public String get(CharSequence name) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return HeadersUtils.getAsString(headers, name);
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2014-10-31 08:48:28 +01:00
|
|
|
@Override
|
|
|
|
public Integer getInt(CharSequence name) {
|
|
|
|
return headers.getInt(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getInt(CharSequence name, int defaultValue) {
|
|
|
|
return headers.getInt(name, defaultValue);
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
@Override
|
|
|
|
public Short getShort(CharSequence name) {
|
|
|
|
return headers.getShort(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public short getShort(CharSequence name, short defaultValue) {
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
return headers.getShort(name, defaultValue);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2014-10-31 08:48:28 +01:00
|
|
|
@Override
|
2014-10-31 19:08:59 +01:00
|
|
|
public Long getTimeMillis(CharSequence name) {
|
|
|
|
return headers.getTimeMillis(name);
|
2014-10-31 08:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-10-31 19:08:59 +01:00
|
|
|
public long getTimeMillis(CharSequence name, long defaultValue) {
|
|
|
|
return headers.getTimeMillis(name, defaultValue);
|
2014-10-31 08:48:28 +01:00
|
|
|
}
|
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public List<String> getAll(String name) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return getAll((CharSequence) name);
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public List<String> getAll(CharSequence name) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return HeadersUtils.getAllAsString(headers, name);
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
public List<Entry<String, String>> entries() {
|
|
|
|
if (isEmpty()) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
List<Entry<String, String>> entriesConverted = new ArrayList<Entry<String, String>>(
|
|
|
|
headers.size());
|
|
|
|
for (Entry<String, String> entry : this) {
|
|
|
|
entriesConverted.add(entry);
|
|
|
|
}
|
|
|
|
return entriesConverted;
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
@Deprecated
|
2015-08-13 04:05:37 +02:00
|
|
|
@Override
|
2013-01-16 05:22:50 +01:00
|
|
|
public Iterator<Map.Entry<String, String>> iterator() {
|
2015-08-13 04:05:37 +02:00
|
|
|
return HeadersUtils.iteratorAsString(headers);
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2015-08-13 04:05:37 +02:00
|
|
|
@Override
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
public Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence() {
|
|
|
|
return headers.iterator();
|
|
|
|
}
|
|
|
|
|
2017-11-12 03:22:22 +01:00
|
|
|
@Override
|
|
|
|
public Iterator<String> valueStringIterator(CharSequence name) {
|
|
|
|
final Iterator<CharSequence> itr = valueCharSequenceIterator(name);
|
|
|
|
return new Iterator<String>() {
|
|
|
|
@Override
|
|
|
|
public boolean hasNext() {
|
|
|
|
return itr.hasNext();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String next() {
|
|
|
|
return itr.next().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void remove() {
|
|
|
|
itr.remove();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
|
|
|
|
return headers.valueIterator(name);
|
|
|
|
}
|
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
@Override
|
|
|
|
public boolean contains(String name) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return contains((CharSequence) name);
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2013-11-28 08:15:14 +01:00
|
|
|
@Override
|
|
|
|
public boolean contains(CharSequence name) {
|
2014-06-05 11:31:04 +02:00
|
|
|
return headers.contains(name);
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
2013-01-16 05:22:50 +01:00
|
|
|
@Override
|
|
|
|
public boolean isEmpty() {
|
2014-06-05 11:31:04 +02:00
|
|
|
return headers.isEmpty();
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
@Override
|
|
|
|
public int size() {
|
|
|
|
return headers.size();
|
|
|
|
}
|
|
|
|
|
2013-10-11 21:28:02 +02:00
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public boolean contains(String name, String value, boolean ignoreCase) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return contains((CharSequence) name, (CharSequence) value, ignoreCase);
|
2013-11-28 08:15:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2014-06-05 11:31:04 +02:00
|
|
|
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return headers.contains(name, value, ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
|
2013-10-11 21:28:02 +02:00
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
@Override
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
public Set<String> names() {
|
2015-08-13 04:05:37 +02:00
|
|
|
return HeadersUtils.namesAsString(headers);
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
@Override
|
2015-08-13 04:05:37 +02:00
|
|
|
public boolean equals(Object o) {
|
2016-11-21 22:18:47 +01:00
|
|
|
return o instanceof DefaultHttpHeaders
|
|
|
|
&& headers.equals(((DefaultHttpHeaders) o).headers, CASE_SENSITIVE_HASHER);
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
}
|
|
|
|
|
2015-08-13 04:05:37 +02:00
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
return headers.hashCode(CASE_SENSITIVE_HASHER);
|
|
|
|
}
|
|
|
|
|
2017-12-13 20:26:35 +01:00
|
|
|
@Override
|
|
|
|
public HttpHeaders copy() {
|
|
|
|
return new DefaultHttpHeaders(headers.copy());
|
|
|
|
}
|
|
|
|
|
2015-10-27 21:47:47 +01:00
|
|
|
private static void validateHeaderNameElement(byte value) {
|
|
|
|
switch (value) {
|
|
|
|
case 0x00:
|
2015-08-13 04:05:37 +02:00
|
|
|
case '\t':
|
|
|
|
case '\n':
|
|
|
|
case 0x0b:
|
|
|
|
case '\f':
|
|
|
|
case '\r':
|
|
|
|
case ' ':
|
|
|
|
case ',':
|
|
|
|
case ':':
|
|
|
|
case ';':
|
|
|
|
case '=':
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
2015-10-27 21:47:47 +01:00
|
|
|
value);
|
2015-08-13 04:05:37 +02:00
|
|
|
default:
|
|
|
|
// Check to see if the character is not an ASCII character, or invalid
|
2015-10-27 21:47:47 +01:00
|
|
|
if (value < 0) {
|
2015-08-13 04:05:37 +02:00
|
|
|
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
|
2015-10-27 21:47:47 +01:00
|
|
|
value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void validateHeaderNameElement(char value) {
|
|
|
|
switch (value) {
|
|
|
|
case 0x00:
|
|
|
|
case '\t':
|
|
|
|
case '\n':
|
|
|
|
case 0x0b:
|
|
|
|
case '\f':
|
|
|
|
case '\r':
|
|
|
|
case ' ':
|
|
|
|
case ',':
|
|
|
|
case ':':
|
|
|
|
case ';':
|
|
|
|
case '=':
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
|
|
|
value);
|
|
|
|
default:
|
|
|
|
// Check to see if the character is not an ASCII character, or invalid
|
|
|
|
if (value > 127) {
|
|
|
|
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
|
|
|
|
value);
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
}
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
2015-08-13 04:05:37 +02:00
|
|
|
static ValueConverter<CharSequence> valueConverter(boolean validate) {
|
|
|
|
return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
static NameValidator<CharSequence> nameValidator(boolean validate) {
|
|
|
|
return validate ? HttpNameValidator : NameValidator.NOT_NULL;
|
|
|
|
}
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
|
2015-08-13 04:05:37 +02:00
|
|
|
private static class HeaderValueConverter extends CharSequenceValueConverter {
|
|
|
|
static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public CharSequence convertObject(Object value) {
|
|
|
|
if (value instanceof CharSequence) {
|
2015-08-13 04:05:37 +02:00
|
|
|
return (CharSequence) value;
|
|
|
|
}
|
|
|
|
if (value instanceof Date) {
|
2016-11-21 21:29:44 +01:00
|
|
|
return DateFormatter.format((Date) value);
|
2015-08-13 04:05:37 +02:00
|
|
|
}
|
|
|
|
if (value instanceof Calendar) {
|
2016-11-21 21:29:44 +01:00
|
|
|
return DateFormatter.format(((Calendar) value).getTime());
|
2015-08-13 04:05:37 +02:00
|
|
|
}
|
|
|
|
return value.toString();
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
|
2015-08-13 04:05:37 +02:00
|
|
|
static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation:
We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory
and that also at least the performance of randomly accessing a header is quite poor. The main
concern however was memory usage, as profiling has shown that a DefaultHttp2Headers
not only use a lot of memory it also wastes a lot due to the underlying hashmaps having
to be resized potentially several times as new headers are being inserted.
This is tracked as issue #3600.
Modifications:
We redesigned the DefaultHeaders to simply take a Map object in its constructor and
reimplemented the class using only the Map primitives. That way the implementation
is very concise and hopefully easy to understand and it allows each concrete headers
implementation to provide its own map or to even use a different headers implementation
for processing requests and writing responses i.e. incoming headers need to provide
fast random access while outgoing headers need fast insertion and fast iteration. The
new implementation can support this with hardly any code changes. It also comes
with the advantage that if the Netty project decides to add a third party collections library
as a dependency, one can simply plug in one of those very fast and memory efficient map
implementations and get faster and smaller headers for free.
For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers.
Result:
- Significantly fewer lines of code in the implementation. While the total commit is still
roughly 400 lines less, the actual implementation is a lot less. I just added some more
tests and microbenchmarks.
- Overall performance is up. The current implementation should be significantly faster
for insertion and retrieval. However, it is slower when it comes to iteration. There is simply
no way a TreeMap can have the same iteration performance as a linked list (as used in the
current headers implementation). That's totally fine though, because when looking at the
benchmark results @ejona86 pointed out that the performance of the headers is completely
dominated by insertion, that is insertion is so significantly faster in the new implementation
that it does make up for several times the iteration speed. You can't iterate what you haven't
inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is
only down for HTTP, it's significantly improved for HTTP/2).
- Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not
produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction
of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data
was generated by [2] using JOL.
- While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly
improved for HTTP, SPDY and STOMP as they all share a common implementation.
[1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0
[2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
2015-04-15 06:14:00 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public CharSequence convertObject(Object value) {
|
|
|
|
CharSequence seq = super.convertObject(value);
|
|
|
|
int state = 0;
|
|
|
|
// Start looping through each of the character
|
|
|
|
for (int index = 0; index < seq.length(); index++) {
|
|
|
|
state = validateValueChar(seq, state, seq.charAt(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state != 0) {
|
|
|
|
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
|
|
|
|
}
|
|
|
|
return seq;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static int validateValueChar(CharSequence seq, int state, char character) {
|
|
|
|
/*
|
|
|
|
* State:
|
|
|
|
* 0: Previous character was neither CR nor LF
|
|
|
|
* 1: The previous character was CR
|
|
|
|
* 2: The previous character was LF
|
|
|
|
*/
|
|
|
|
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
|
|
|
|
// Check the absolutely prohibited characters.
|
|
|
|
switch (character) {
|
|
|
|
case 0x0: // NULL
|
|
|
|
throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
|
|
|
|
case 0x0b: // Vertical tab
|
|
|
|
throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
|
|
|
|
case '\f':
|
|
|
|
throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the CRLF (HT | SP) pattern
|
|
|
|
switch (state) {
|
|
|
|
case 0:
|
|
|
|
switch (character) {
|
|
|
|
case '\r':
|
|
|
|
return 1;
|
|
|
|
case '\n':
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
switch (character) {
|
|
|
|
case '\n':
|
|
|
|
return 2;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
|
|
|
|
}
|
|
|
|
case 2:
|
|
|
|
switch (character) {
|
|
|
|
case '\t':
|
|
|
|
case ' ':
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return state;
|
|
|
|
}
|
2014-04-24 14:13:19 +02:00
|
|
|
}
|
2013-01-16 05:22:50 +01:00
|
|
|
}
|