From 9bd6d8129ef12ff509e7b5c2b331bbff1c271d40 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Mon, 28 Aug 2017 17:58:00 -0700 Subject: [PATCH] HttpObjectEncoder buffer size estimation 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. --- .../handler/codec/http/HttpObjectEncoder.java | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java index 72e9ef6026..bde433a630 100755 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java @@ -53,6 +53,10 @@ public abstract class HttpObjectEncoder 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; @@ -62,6 +66,18 @@ public abstract class HttpObjectEncoder 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 out) throws Exception { ByteBuf buf = null; @@ -73,10 +89,12 @@ public abstract class HttpObjectEncoder extends MessageTo @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; @@ -177,10 +195,12 @@ public abstract class HttpObjectEncoder extends MessageTo 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) { @@ -232,6 +252,16 @@ public abstract class HttpObjectEncoder extends MessageTo 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);