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.
This commit is contained in:
parent
7528e5a11e
commit
9bd6d8129e
@ -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;
|
||||
@ -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;
|
||||
@ -73,10 +89,12 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> 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<H extends HttpMessage> 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<H extends HttpMessage> 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);
|
||||
|
Loading…
Reference in New Issue
Block a user