Reduce http2 buffer slicing (#8598)

Motivation

DefaultHttp2FrameReader currently does a fair amount of "intermediate"
slicing which can be avoided.

Modifications

Avoid slicing the input buffer in DefaultHttp2FrameReader until
necessary. In one instance this also means retainedSlice can be used
instead (which may also avoid allocating).

Results

Less allocations when using http2.
This commit is contained in:
Nick Hill 2018-11-29 10:45:52 -08:00 committed by Norman Maurer
parent fc0a4e15ab
commit 0ecd0c6ff3
2 changed files with 50 additions and 52 deletions

View File

@ -239,8 +239,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
return; return;
} }
// Get a view of the buffer for the size of the payload. // Only process up to payloadLength bytes.
ByteBuf payload = in.readSlice(payloadLength); int payloadEndIndex = in.readerIndex() + payloadLength;
// We have consumed the data, next time we read we will be expecting to read a frame header. // We have consumed the data, next time we read we will be expecting to read a frame header.
readingHeaders = true; readingHeaders = true;
@ -248,39 +248,40 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
// Read the payload and fire the frame event to the listener. // Read the payload and fire the frame event to the listener.
switch (frameType) { switch (frameType) {
case DATA: case DATA:
readDataFrame(ctx, payload, listener); readDataFrame(ctx, in, payloadEndIndex, listener);
break; break;
case HEADERS: case HEADERS:
readHeadersFrame(ctx, payload, listener); readHeadersFrame(ctx, in, payloadEndIndex, listener);
break; break;
case PRIORITY: case PRIORITY:
readPriorityFrame(ctx, payload, listener); readPriorityFrame(ctx, in, listener);
break; break;
case RST_STREAM: case RST_STREAM:
readRstStreamFrame(ctx, payload, listener); readRstStreamFrame(ctx, in, listener);
break; break;
case SETTINGS: case SETTINGS:
readSettingsFrame(ctx, payload, listener); readSettingsFrame(ctx, in, listener);
break; break;
case PUSH_PROMISE: case PUSH_PROMISE:
readPushPromiseFrame(ctx, payload, listener); readPushPromiseFrame(ctx, in, payloadEndIndex, listener);
break; break;
case PING: case PING:
readPingFrame(ctx, payload.readLong(), listener); readPingFrame(ctx, in.readLong(), listener);
break; break;
case GO_AWAY: case GO_AWAY:
readGoAwayFrame(ctx, payload, listener); readGoAwayFrame(ctx, in, payloadEndIndex, listener);
break; break;
case WINDOW_UPDATE: case WINDOW_UPDATE:
readWindowUpdateFrame(ctx, payload, listener); readWindowUpdateFrame(ctx, in, listener);
break; break;
case CONTINUATION: case CONTINUATION:
readContinuationFrame(payload, listener); readContinuationFrame(in, payloadEndIndex, listener);
break; break;
default: default:
readUnknownFrame(ctx, payload, listener); readUnknownFrame(ctx, in, payloadEndIndex, listener);
break; break;
} }
in.readerIndex(payloadEndIndex);
} }
private void verifyDataFrame() throws Http2Exception { private void verifyDataFrame() throws Http2Exception {
@ -408,21 +409,20 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
verifyNotProcessingHeaders(); verifyNotProcessingHeaders();
} }
private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload, private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload, int payloadEndIndex,
Http2FrameListener listener) throws Http2Exception { Http2FrameListener listener) throws Http2Exception {
int padding = readPadding(payload); int padding = readPadding(payload);
verifyPadding(padding); verifyPadding(padding);
// Determine how much data there is to read by removing the trailing // Determine how much data there is to read by removing the trailing
// padding. // padding.
int dataLength = lengthWithoutTrailingPadding(payload.readableBytes(), padding); int dataLength = lengthWithoutTrailingPadding(payloadEndIndex - payload.readerIndex(), padding);
ByteBuf data = payload.readSlice(dataLength); ByteBuf data = payload.readSlice(dataLength);
listener.onDataRead(ctx, streamId, data, padding, flags.endOfStream()); listener.onDataRead(ctx, streamId, data, padding, flags.endOfStream());
payload.skipBytes(payload.readableBytes());
} }
private void readHeadersFrame(final ChannelHandlerContext ctx, ByteBuf payload, private void readHeadersFrame(final ChannelHandlerContext ctx, ByteBuf payload, int payloadEndIndex,
Http2FrameListener listener) throws Http2Exception { Http2FrameListener listener) throws Http2Exception {
final int headersStreamId = streamId; final int headersStreamId = streamId;
final Http2Flags headersFlags = flags; final Http2Flags headersFlags = flags;
@ -439,7 +439,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself."); throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself.");
} }
final short weight = (short) (payload.readUnsignedByte() + 1); final short weight = (short) (payload.readUnsignedByte() + 1);
final ByteBuf fragment = payload.readSlice(lengthWithoutTrailingPadding(payload.readableBytes(), padding)); final int lenToRead = lengthWithoutTrailingPadding(payloadEndIndex - payload.readerIndex(), padding);
// Create a handler that invokes the listener when the header block is complete. // Create a handler that invokes the listener when the header block is complete.
headersContinuation = new HeadersContinuation() { headersContinuation = new HeadersContinuation() {
@ -449,10 +449,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
} }
@Override @Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
Http2FrameListener listener) throws Http2Exception { Http2FrameListener listener) throws Http2Exception {
final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder(); final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
hdrBlockBuilder.addFragment(fragment, ctx.alloc(), endOfHeaders); hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
if (endOfHeaders) { if (endOfHeaders) {
listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), streamDependency, listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), streamDependency,
weight, exclusive, padding, headersFlags.endOfStream()); weight, exclusive, padding, headersFlags.endOfStream());
@ -461,7 +461,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
}; };
// Process the initial fragment, invoking the listener's callback if end of headers. // Process the initial fragment, invoking the listener's callback if end of headers.
headersContinuation.processFragment(flags.endOfHeaders(), fragment, listener); headersContinuation.processFragment(flags.endOfHeaders(), payload, lenToRead, listener);
resetHeadersContinuationIfEnd(flags.endOfHeaders()); resetHeadersContinuationIfEnd(flags.endOfHeaders());
return; return;
} }
@ -475,10 +475,10 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
} }
@Override @Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
Http2FrameListener listener) throws Http2Exception { Http2FrameListener listener) throws Http2Exception {
final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder(); final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
hdrBlockBuilder.addFragment(fragment, ctx.alloc(), endOfHeaders); hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
if (endOfHeaders) { if (endOfHeaders) {
listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), padding, listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), padding,
headersFlags.endOfStream()); headersFlags.endOfStream());
@ -487,8 +487,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
}; };
// Process the initial fragment, invoking the listener's callback if end of headers. // Process the initial fragment, invoking the listener's callback if end of headers.
final ByteBuf fragment = payload.readSlice(lengthWithoutTrailingPadding(payload.readableBytes(), padding)); int len = lengthWithoutTrailingPadding(payloadEndIndex - payload.readerIndex(), padding);
headersContinuation.processFragment(flags.endOfHeaders(), fragment, listener); headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
resetHeadersContinuationIfEnd(flags.endOfHeaders()); resetHeadersContinuationIfEnd(flags.endOfHeaders());
} }
@ -543,7 +543,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
} }
} }
private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload, private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload, int payloadEndIndex,
Http2FrameListener listener) throws Http2Exception { Http2FrameListener listener) throws Http2Exception {
final int pushPromiseStreamId = streamId; final int pushPromiseStreamId = streamId;
final int padding = readPadding(payload); final int padding = readPadding(payload);
@ -558,9 +558,9 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
} }
@Override @Override
public void processFragment(boolean endOfHeaders, ByteBuf fragment, public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
Http2FrameListener listener) throws Http2Exception { Http2FrameListener listener) throws Http2Exception {
headersBlockBuilder().addFragment(fragment, ctx.alloc(), endOfHeaders); headersBlockBuilder().addFragment(fragment, len, ctx.alloc(), endOfHeaders);
if (endOfHeaders) { if (endOfHeaders) {
listener.onPushPromiseRead(ctx, pushPromiseStreamId, promisedStreamId, listener.onPushPromiseRead(ctx, pushPromiseStreamId, promisedStreamId,
headersBlockBuilder().headers(), padding); headersBlockBuilder().headers(), padding);
@ -569,8 +569,8 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
}; };
// Process the initial fragment, invoking the listener's callback if end of headers. // Process the initial fragment, invoking the listener's callback if end of headers.
final ByteBuf fragment = payload.readSlice(lengthWithoutTrailingPadding(payload.readableBytes(), padding)); int len = lengthWithoutTrailingPadding(payloadEndIndex - payload.readerIndex(), padding);
headersContinuation.processFragment(flags.endOfHeaders(), fragment, listener); headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
resetHeadersContinuationIfEnd(flags.endOfHeaders()); resetHeadersContinuationIfEnd(flags.endOfHeaders());
} }
@ -583,11 +583,11 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
} }
} }
private static void readGoAwayFrame(ChannelHandlerContext ctx, ByteBuf payload, private static void readGoAwayFrame(ChannelHandlerContext ctx, ByteBuf payload, int payloadEndIndex,
Http2FrameListener listener) throws Http2Exception { Http2FrameListener listener) throws Http2Exception {
int lastStreamId = readUnsignedInt(payload); int lastStreamId = readUnsignedInt(payload);
long errorCode = payload.readUnsignedInt(); long errorCode = payload.readUnsignedInt();
ByteBuf debugData = payload.readSlice(payload.readableBytes()); ByteBuf debugData = payload.readSlice(payloadEndIndex - payload.readerIndex());
listener.onGoAwayRead(ctx, lastStreamId, errorCode, debugData); listener.onGoAwayRead(ctx, lastStreamId, errorCode, debugData);
} }
@ -601,18 +601,17 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement); listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
} }
private void readContinuationFrame(ByteBuf payload, Http2FrameListener listener) private void readContinuationFrame(ByteBuf payload, int payloadEndIndex, Http2FrameListener listener)
throws Http2Exception { throws Http2Exception {
// Process the initial fragment, invoking the listener's callback if end of headers. // Process the initial fragment, invoking the listener's callback if end of headers.
final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes()); headersContinuation.processFragment(flags.endOfHeaders(), payload,
headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment, payloadEndIndex - payload.readerIndex(), listener);
listener);
resetHeadersContinuationIfEnd(flags.endOfHeaders()); resetHeadersContinuationIfEnd(flags.endOfHeaders());
} }
private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload, Http2FrameListener listener) private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload,
throws Http2Exception { int payloadEndIndex, Http2FrameListener listener) throws Http2Exception {
payload = payload.readSlice(payload.readableBytes()); payload = payload.readSlice(payloadEndIndex - payload.readerIndex());
listener.onUnknownFrame(ctx, frameType, streamId, flags, payload); listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
} }
@ -664,7 +663,7 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
* @param fragment the fragment of the header block to be added. * @param fragment the fragment of the header block to be added.
* @param listener the listener to be notified if the header block is completed. * @param listener the listener to be notified if the header block is completed.
*/ */
abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
Http2FrameListener listener) throws Http2Exception; Http2FrameListener listener) throws Http2Exception;
final HeadersBlockBuilder headersBlockBuilder() { final HeadersBlockBuilder headersBlockBuilder() {
@ -704,33 +703,32 @@ public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSize
* This is used for an optimization for when the first fragment is the full * This is used for an optimization for when the first fragment is the full
* block. In that case, the buffer is used directly without copying. * block. In that case, the buffer is used directly without copying.
*/ */
final void addFragment(ByteBuf fragment, ByteBufAllocator alloc, boolean endOfHeaders) throws Http2Exception { final void addFragment(ByteBuf fragment, int len, ByteBufAllocator alloc,
boolean endOfHeaders) throws Http2Exception {
if (headerBlock == null) { if (headerBlock == null) {
if (fragment.readableBytes() > headersDecoder.configuration().maxHeaderListSizeGoAway()) { if (len > headersDecoder.configuration().maxHeaderListSizeGoAway()) {
headerSizeExceeded(); headerSizeExceeded();
} }
if (endOfHeaders) { if (endOfHeaders) {
// Optimization - don't bother copying, just use the buffer as-is. Need // Optimization - don't bother copying, just use the buffer as-is. Need
// to retain since we release when the header block is built. // to retain since we release when the header block is built.
headerBlock = fragment.retain(); headerBlock = fragment.readRetainedSlice(len);
} else { } else {
headerBlock = alloc.buffer(fragment.readableBytes()); headerBlock = alloc.buffer(len).writeBytes(fragment, len);
headerBlock.writeBytes(fragment);
} }
return; return;
} }
if (headersDecoder.configuration().maxHeaderListSizeGoAway() - fragment.readableBytes() < if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
headerBlock.readableBytes()) { headerBlock.readableBytes()) {
headerSizeExceeded(); headerSizeExceeded();
} }
if (headerBlock.isWritable(fragment.readableBytes())) { if (headerBlock.isWritable(len)) {
// The buffer can hold the requested bytes, just write it directly. // The buffer can hold the requested bytes, just write it directly.
headerBlock.writeBytes(fragment); headerBlock.writeBytes(fragment, len);
} else { } else {
// Allocate a new buffer that is big enough to hold the entire header block so far. // Allocate a new buffer that is big enough to hold the entire header block so far.
ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + fragment.readableBytes()); ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + len);
buf.writeBytes(headerBlock); buf.writeBytes(headerBlock).writeBytes(fragment, len);
buf.writeBytes(fragment);
headerBlock.release(); headerBlock.release();
headerBlock = buf; headerBlock = buf;
} }

View File

@ -195,7 +195,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize
ctx.write(lastFrame, promiseAggregator.newPromise()); ctx.write(lastFrame, promiseAggregator.newPromise());
// Write the payload. // Write the payload.
lastFrame = data.readSlice(maxFrameSize); lastFrame = data.readableBytes() != maxFrameSize ? data.readSlice(maxFrameSize) : data;
data = null; data = null;
ctx.write(lastFrame, promiseAggregator.newPromise()); ctx.write(lastFrame, promiseAggregator.newPromise());
} }