Skip to content

Commit

Permalink
HttpObjectEncoder buffer size estimation
Browse files Browse the repository at this point in the history
Motivation:
HttpObjectEncoder allocates a new buffer when encoding the initial line and headers, and also allocates a buffer when encoding the trailers. The allocation always uses the default size of 256. This may lead to consistent under allocation and require a few resize/copy operations which can cause GC/memory pressure.

Modifications:
- Introduce a weighted average which tracks the historical size of encoded data and uses this as an estimate for future buffer allocations

Result:
Better approximation of buffer sizes.
  • Loading branch information
Scottmitch committed Aug 31, 2017
1 parent 7528e5a commit 9bd6d81
Showing 1 changed file with 32 additions and 2 deletions.
Expand Up @@ -53,6 +53,10 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
private static final ByteBuf CRLF_BUF = unreleasableBuffer(directBuffer(2).writeByte(CR).writeByte(LF));
private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length)
.writeBytes(ZERO_CRLF_CRLF));
private static final float HEADERS_WEIGHT_NEW = 1 / 5f;
private static final float HEADERS_WEIGHT_HISTORICAL = 1 - HEADERS_WEIGHT_NEW;
private static final float TRAILERS_WEIGHT_NEW = HEADERS_WEIGHT_NEW;
private static final float TRAILERS_WEIGHT_HISTORICAL = HEADERS_WEIGHT_HISTORICAL;

private static final int ST_INIT = 0;
private static final int ST_CONTENT_NON_CHUNK = 1;
Expand All @@ -62,6 +66,18 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
@SuppressWarnings("RedundantFieldInitialization")
private int state = ST_INIT;

/**
* Used to calculate an exponential moving average of the encoded size of the initial line and the headers for
* a guess for future buffer allocations.
*/
private float headersEncodedSizeAccumulator = 256;

/**
* Used to calculate an exponential moving average of the encoded size of the trailers for
* a guess for future buffer allocations.
*/
private float trailersEncodedSizeAccumulator = 256;

@Override
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
ByteBuf buf = null;
Expand All @@ -73,10 +89,12 @@ protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) t
@SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
H m = (H) msg;

buf = ctx.alloc().buffer();
buf = ctx.alloc().buffer((int) headersEncodedSizeAccumulator);
// Encode the message.
encodeInitialLine(buf, m);
encodeHeaders(m.headers(), buf);
headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
Expand Down Expand Up @@ -177,10 +195,12 @@ private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long co
if (headers.isEmpty()) {
out.add(ZERO_CRLF_CRLF_BUF.duplicate());
} else {
ByteBuf buf = ctx.alloc().buffer();
ByteBuf buf = ctx.alloc().buffer((int) trailersEncodedSizeAccumulator);
ByteBufUtil.writeMediumBE(buf, ZERO_CRLF_MEDIUM);
encodeHeaders(headers, buf);
ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
trailersEncodedSizeAccumulator = TRAILERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
TRAILERS_WEIGHT_HISTORICAL * trailersEncodedSizeAccumulator;
out.add(buf);
}
} else if (contentLength == 0) {
Expand Down Expand Up @@ -232,6 +252,16 @@ private static long contentLength(Object msg) {
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
}

/**
* Add some additional overhead to the buffer. The rational is that it is better to slightly over allocate and waste
* some memory, rather than under allocate and require a resize/copy.
* @param readableBytes The readable bytes in the buffer.
* @return The {@code readableBytes} with some additional padding.
*/
private static int padSizeForAccumulation(int readableBytes) {
return (readableBytes << 2) / 3;
}

@Deprecated
protected static void encodeAscii(String s, ByteBuf buf) {
buf.writeCharSequence(s, CharsetUtil.US_ASCII);
Expand Down

0 comments on commit 9bd6d81

Please sign in to comment.