Skip to content

Commit 707c95e

Browse files
authored
Use ByteProcessor in HpackHuffmanDecoder to reduce bound-checks and r… (netty#9317)
Motivation: ff0045e changed HpackHuffmanDecoder to use a lookup-table which greatly improved performance. We can squeeze out another 3% win by using an ByteProcessor which will reduce the number of bound-checks / reference-count-checks needed by processing byte-by-byte. Modifications: Implement logic with ByteProcessor Result: Another ~3% perf improvement which shows up when using h2load to simulate load. `h2load -c 100 -m 100 --duration 60 --warm-up-time 10 http://127.0.0.1:8080` Before: ``` finished in 70.02s, 620051.67 req/s, 20.70MB/s requests: 37203100 total, 37203100 started, 37203100 done, 37203100 succeeded, 0 failed, 0 errored, 0 timeout status codes: 37203100 2xx, 0 3xx, 0 4xx, 0 5xx traffic: 1.21GB (1302108500) total, 41.84MB (43872600) headers (space savings 90.00%), 460.24MB (482598600) data min max mean sd +/- sd time for request: 404us 24.52ms 15.93ms 1.45ms 87.90% time for connect: 0us 0us 0us 0us 0.00% time to 1st byte: 0us 0us 0us 0us 0.00% req/s : 6186.64 6211.60 6199.00 5.18 65.00% ``` With this change: ``` finished in 70.02s, 642103.33 req/s, 21.43MB/s requests: 38526200 total, 38526200 started, 38526200 done, 38526200 succeeded, 0 failed, 0 errored, 0 timeout status codes: 38526200 2xx, 0 3xx, 0 4xx, 0 5xx traffic: 1.26GB (1348417000) total, 42.39MB (44444900) headers (space savings 90.00%), 466.25MB (488893900) data min max mean sd +/- sd time for request: 370us 24.89ms 15.52ms 1.35ms 88.02% time for connect: 0us 0us 0us 0us 0.00% time to 1st byte: 0us 0us 0us 0us 0.00% req/s : 6407.06 6435.19 6419.74 5.62 67.00% ```
1 parent 16b98d3 commit 707c95e

File tree

3 files changed

+62
-33
lines changed

3 files changed

+62
-33
lines changed

codec-http2/src/main/java/io/netty/handler/codec/http2/HpackDecoder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ final class HpackDecoder {
8989
private static final byte READ_LITERAL_HEADER_VALUE_LENGTH = 8;
9090
private static final byte READ_LITERAL_HEADER_VALUE = 9;
9191

92+
private final HpackHuffmanDecoder huffmanDecoder = new HpackHuffmanDecoder();
9293
private final HpackDynamicTable hpackDynamicTable;
9394
private long maxHeaderListSize;
9495
private long maxDynamicTableSize;
@@ -445,7 +446,7 @@ private void insertHeader(Sink sink, CharSequence name, CharSequence value, Inde
445446

446447
private CharSequence readStringLiteral(ByteBuf in, int length, boolean huffmanEncoded) throws Http2Exception {
447448
if (huffmanEncoded) {
448-
return HpackHuffmanDecoder.decode(in, length);
449+
return huffmanDecoder.decode(in, length);
449450
}
450451
byte[] buf = new byte[length];
451452
in.readBytes(buf);

codec-http2/src/main/java/io/netty/handler/codec/http2/HpackHuffmanDecoder.java

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@
3333

3434
import io.netty.buffer.ByteBuf;
3535
import io.netty.util.AsciiString;
36+
import io.netty.util.ByteProcessor;
3637
import io.netty.util.internal.ThrowableUtil;
3738

3839
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
3940

40-
final class HpackHuffmanDecoder {
41+
final class HpackHuffmanDecoder implements ByteProcessor {
4142

4243
/* Scroll to the bottom! */
4344

@@ -4668,50 +4669,77 @@ final class HpackHuffmanDecoder {
46684669
Http2Exception.newStatic(COMPRESSION_ERROR, "HPACK - Bad Encoding",
46694670
Http2Exception.ShutdownHint.HARD_SHUTDOWN), HpackHuffmanDecoder.class, "decode(..)");
46704671

4672+
private byte[] dest;
4673+
private int k;
4674+
private int state;
4675+
private int flags;
4676+
4677+
HpackHuffmanDecoder() { }
4678+
46714679
/**
46724680
* Decompresses the given Huffman coded string literal.
46734681
*
46744682
* @param buf the string literal to be decoded
46754683
* @return the output stream for the compressed data
46764684
* @throws Http2Exception EOS Decoded
46774685
*/
4678-
public static AsciiString decode(ByteBuf buf, int length) throws Http2Exception {
4686+
public AsciiString decode(ByteBuf buf, int length) throws Http2Exception {
46794687
if (length == 0) {
46804688
return AsciiString.EMPTY_STRING;
46814689
}
4682-
byte[] dest = new byte[length * 8 / 5];
4683-
int k = 0;
4684-
int state = 0;
4685-
int flags = 0;
4686-
for (int i = 0; i < length; i++) {
4687-
byte input = buf.readByte();
4688-
int index = (state << 4) | ((input & 0xFF) >>> 4);
4689-
int row = HUFFS[index];
4690-
flags = row & 0x00FF00;
4691-
if ((flags & HUFFMAN_FAIL_SHIFT) != 0) {
4692-
throw BAD_ENCODING;
4690+
dest = new byte[length * 8 / 5];
4691+
try {
4692+
int readerIndex = buf.readerIndex();
4693+
// Using ByteProcessor to reduce bounds-checking and reference-count checking during byte-by-byte
4694+
// processing of the ByteBuf.
4695+
int endIndex = buf.forEachByte(readerIndex, length, this);
4696+
if (endIndex == -1) {
4697+
// We did consume the requested length
4698+
buf.readerIndex(readerIndex + length);
4699+
if ((flags & HUFFMAN_COMPLETE_SHIFT) != HUFFMAN_COMPLETE_SHIFT) {
4700+
throw BAD_ENCODING;
4701+
}
4702+
return new AsciiString(dest, 0, k, false);
46934703
}
4694-
if ((flags & HUFFMAN_EMIT_SYMBOL_SHIFT) != 0) {
4695-
dest[k++] = (byte) (row & 0xFF);
4696-
}
4697-
state = row >> 16;
46984704

4699-
index = (state << 4) | (input & 0x0F);
4700-
row = HUFFS[index];
4701-
flags = row & 0x00FF00;
4702-
if ((flags & HUFFMAN_FAIL_SHIFT) != 0) {
4703-
throw BAD_ENCODING;
4704-
}
4705-
if ((flags & HUFFMAN_EMIT_SYMBOL_SHIFT) != 0) {
4706-
dest[k++] = (byte) (row & 0xFF);
4707-
}
4708-
state = row >> 16;
4709-
}
4710-
if ((flags & HUFFMAN_COMPLETE_SHIFT) != HUFFMAN_COMPLETE_SHIFT) {
4705+
// The process(...) method returned before the requested length was requested. This means there
4706+
// was a bad encoding detected.
4707+
buf.readerIndex(endIndex);
47114708
throw BAD_ENCODING;
4709+
} finally {
4710+
dest = null;
4711+
k = 0;
4712+
state = 0;
4713+
flags = 0;
47124714
}
4713-
return new AsciiString(dest, 0, k, false);
47144715
}
47154716

4716-
private HpackHuffmanDecoder() { }
4717+
/**
4718+
* <strong>This should never be called from anything but this class itself!</strong>
4719+
*/
4720+
@Override
4721+
public boolean process(byte input) {
4722+
int index = (state << 4) | ((input & 0xFF) >>> 4);
4723+
int row = HUFFS[index];
4724+
flags = row & 0x00FF00;
4725+
if ((flags & HUFFMAN_FAIL_SHIFT) != 0) {
4726+
return false;
4727+
}
4728+
if ((flags & HUFFMAN_EMIT_SYMBOL_SHIFT) != 0) {
4729+
dest[k++] = (byte) (row & 0xFF);
4730+
}
4731+
state = row >> 16;
4732+
4733+
index = (state << 4) | (input & 0x0F);
4734+
row = HUFFS[index];
4735+
flags = row & 0x00FF00;
4736+
if ((flags & HUFFMAN_FAIL_SHIFT) != 0) {
4737+
return false;
4738+
}
4739+
if ((flags & HUFFMAN_EMIT_SYMBOL_SHIFT) != 0) {
4740+
dest[k++] = (byte) (row & 0xFF);
4741+
}
4742+
state = row >> 16;
4743+
return true;
4744+
}
47174745
}

codec-http2/src/test/java/io/netty/handler/codec/http2/HpackHuffmanTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ private static void roundTrip(HpackHuffmanEncoder encoder, byte[] buf)
153153
private static byte[] decode(byte[] bytes) throws Http2Exception {
154154
ByteBuf buffer = Unpooled.wrappedBuffer(bytes);
155155
try {
156-
AsciiString decoded = HpackHuffmanDecoder.decode(buffer, buffer.readableBytes());
156+
AsciiString decoded = new HpackHuffmanDecoder().decode(buffer, buffer.readableBytes());
157157
Assert.assertFalse(buffer.isReadable());
158158
return decoded.toByteArray();
159159
} finally {

0 commit comments

Comments
 (0)