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 CRLF_BUF = unreleasableBuffer(directBuffer(2).writeByte(CR).writeByte(LF));
|
||||||
private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length)
|
private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length)
|
||||||
.writeBytes(ZERO_CRLF_CRLF));
|
.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_INIT = 0;
|
||||||
private static final int ST_CONTENT_NON_CHUNK = 1;
|
private static final int ST_CONTENT_NON_CHUNK = 1;
|
||||||
@ -62,6 +66,18 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
|
|||||||
@SuppressWarnings("RedundantFieldInitialization")
|
@SuppressWarnings("RedundantFieldInitialization")
|
||||||
private int state = ST_INIT;
|
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
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
|
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
|
||||||
ByteBuf buf = null;
|
ByteBuf buf = null;
|
||||||
@ -73,10 +89,12 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
|
|||||||
@SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
|
@SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
|
||||||
H m = (H) msg;
|
H m = (H) msg;
|
||||||
|
|
||||||
buf = ctx.alloc().buffer();
|
buf = ctx.alloc().buffer((int) headersEncodedSizeAccumulator);
|
||||||
// Encode the message.
|
// Encode the message.
|
||||||
encodeInitialLine(buf, m);
|
encodeInitialLine(buf, m);
|
||||||
encodeHeaders(m.headers(), buf);
|
encodeHeaders(m.headers(), buf);
|
||||||
|
headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
|
||||||
|
HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
|
||||||
ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
|
ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
|
||||||
state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
|
state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
|
||||||
HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
|
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()) {
|
if (headers.isEmpty()) {
|
||||||
out.add(ZERO_CRLF_CRLF_BUF.duplicate());
|
out.add(ZERO_CRLF_CRLF_BUF.duplicate());
|
||||||
} else {
|
} else {
|
||||||
ByteBuf buf = ctx.alloc().buffer();
|
ByteBuf buf = ctx.alloc().buffer((int) trailersEncodedSizeAccumulator);
|
||||||
ByteBufUtil.writeMediumBE(buf, ZERO_CRLF_MEDIUM);
|
ByteBufUtil.writeMediumBE(buf, ZERO_CRLF_MEDIUM);
|
||||||
encodeHeaders(headers, buf);
|
encodeHeaders(headers, buf);
|
||||||
ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
|
ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
|
||||||
|
trailersEncodedSizeAccumulator = TRAILERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
|
||||||
|
TRAILERS_WEIGHT_HISTORICAL * trailersEncodedSizeAccumulator;
|
||||||
out.add(buf);
|
out.add(buf);
|
||||||
}
|
}
|
||||||
} else if (contentLength == 0) {
|
} 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));
|
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
|
@Deprecated
|
||||||
protected static void encodeAscii(String s, ByteBuf buf) {
|
protected static void encodeAscii(String s, ByteBuf buf) {
|
||||||
buf.writeCharSequence(s, CharsetUtil.US_ASCII);
|
buf.writeCharSequence(s, CharsetUtil.US_ASCII);
|
||||||
|
Loading…
Reference in New Issue
Block a user