Minimize object allocation when calling AbstractByteBuf.toString(..., Charset)
Motivation: Calling AbstractByteBuf.toString(..., Charset) is used quite frequently by users but produce a lot of GC. Modification: - Use a FastThreadLocal to store the CharBuffer that are needed for decoding. - Use internalNioBuffer(...) when possible Result: Less object creation / Less GC
This commit is contained in:
parent
0236af7995
commit
054af70fed
@ -974,20 +974,7 @@ public abstract class AbstractByteBuf extends ByteBuf {
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
if (length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
ByteBuffer nioBuffer;
|
||||
if (nioBufferCount() == 1) {
|
||||
nioBuffer = nioBuffer(index, length);
|
||||
} else {
|
||||
nioBuffer = ByteBuffer.allocate(length);
|
||||
getBytes(index, nioBuffer);
|
||||
nioBuffer.flip();
|
||||
}
|
||||
|
||||
return ByteBufUtil.decodeString(nioBuffer, charset);
|
||||
return ByteBufUtil.decodeString(this, index, length, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,6 +18,7 @@ package io.netty.buffer;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.Recycler;
|
||||
import io.netty.util.Recycler.Handle;
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
@ -42,7 +43,14 @@ import java.util.Locale;
|
||||
public final class ByteBufUtil {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ByteBufUtil.class);
|
||||
private static final FastThreadLocal<CharBuffer> CHAR_BUFFERS = new FastThreadLocal<CharBuffer>() {
|
||||
@Override
|
||||
protected CharBuffer initialValue() throws Exception {
|
||||
return CharBuffer.allocate(1024);
|
||||
}
|
||||
};
|
||||
|
||||
private static final int MAX_CHAR_BUFFER_SIZE;
|
||||
private static final char[] HEXDUMP_TABLE = new char[256 * 4];
|
||||
private static final String NEWLINE = StringUtil.NEWLINE;
|
||||
private static final String[] BYTE2HEX = new String[256];
|
||||
@ -125,6 +133,9 @@ public final class ByteBufUtil {
|
||||
|
||||
THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", 64 * 1024);
|
||||
logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);
|
||||
|
||||
MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", 16 * 1024);
|
||||
logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -519,10 +530,41 @@ public final class ByteBufUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static String decodeString(ByteBuffer src, Charset charset) {
|
||||
static String decodeString(ByteBuf src, int readerIndex, int len, Charset charset) {
|
||||
if (len == 0) {
|
||||
return StringUtil.EMPTY_STRING;
|
||||
}
|
||||
final CharsetDecoder decoder = CharsetUtil.getDecoder(charset);
|
||||
final CharBuffer dst = CharBuffer.allocate(
|
||||
(int) ((double) src.remaining() * decoder.maxCharsPerByte()));
|
||||
final int maxLength = (int) ((double) len * decoder.maxCharsPerByte());
|
||||
CharBuffer dst = CHAR_BUFFERS.get();
|
||||
if (dst.length() < maxLength) {
|
||||
dst = CharBuffer.allocate(maxLength);
|
||||
if (maxLength <= MAX_CHAR_BUFFER_SIZE) {
|
||||
CHAR_BUFFERS.set(dst);
|
||||
}
|
||||
} else {
|
||||
dst.clear();
|
||||
}
|
||||
if (src.nioBufferCount() == 1) {
|
||||
// Use internalNioBuffer(...) to reduce object creation.
|
||||
decodeString(decoder, src.internalNioBuffer(readerIndex, len), dst);
|
||||
} else {
|
||||
// We use a heap buffer as CharsetDecoder is most likely able to use a fast-path if src and dst buffers
|
||||
// are both backed by a byte array.
|
||||
ByteBuf buffer = src.alloc().heapBuffer(len);
|
||||
try {
|
||||
buffer.writeBytes(src, readerIndex, len);
|
||||
// Use internalNioBuffer(...) to reduce object creation.
|
||||
decodeString(decoder, buffer.internalNioBuffer(readerIndex, len), dst);
|
||||
} finally {
|
||||
// Release the temporary buffer again.
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
return dst.flip().toString();
|
||||
}
|
||||
|
||||
private static void decodeString(CharsetDecoder decoder, ByteBuffer src, CharBuffer dst) {
|
||||
try {
|
||||
CoderResult cr = decoder.decode(src, dst, true);
|
||||
if (!cr.isUnderflow()) {
|
||||
@ -535,7 +577,6 @@ public final class ByteBufUtil {
|
||||
} catch (CharacterCodingException x) {
|
||||
throw new IllegalStateException(x);
|
||||
}
|
||||
return dst.flip().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,8 @@ import io.netty.util.ReferenceCountUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class ByteBufUtilTest {
|
||||
|
||||
@Test
|
||||
@ -73,4 +75,20 @@ public class ByteBufUtilTest {
|
||||
private static void assertWrapped(ByteBuf buf) {
|
||||
Assert.assertTrue(buf instanceof WrappedByteBuf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeUsAscii() {
|
||||
testDecodeString("This is a test", CharsetUtil.US_ASCII);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeUtf8() {
|
||||
testDecodeString("Some UTF-8 like äÄ∏ŒŒ", CharsetUtil.UTF_8);
|
||||
}
|
||||
|
||||
private static void testDecodeString(String text, Charset charset) {
|
||||
ByteBuf buffer = Unpooled.copiedBuffer(text, charset);
|
||||
Assert.assertEquals(text, ByteBufUtil.decodeString(buffer, 0, buffer.readableBytes(), charset));
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
@ -30,12 +30,14 @@ import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@Warmup(iterations = 10)
|
||||
@Measurement(iterations = 25)
|
||||
@Warmup(iterations = 5)
|
||||
@Measurement(iterations = 10)
|
||||
public class
|
||||
ByteBufUtilBenchmark extends AbstractMicrobenchmark {
|
||||
private ByteBuf buffer;
|
||||
private ByteBuf wrapped;
|
||||
private ByteBuf asciiBuffer;
|
||||
private ByteBuf utf8Buffer;
|
||||
|
||||
private StringBuilder asciiSequence;
|
||||
private String ascii;
|
||||
@ -62,12 +64,17 @@ public class
|
||||
}
|
||||
utf8 = utf8Sequence.toString();
|
||||
asciiSequence = utf8Sequence;
|
||||
|
||||
asciiBuffer = Unpooled.copiedBuffer(ascii, CharsetUtil.US_ASCII);
|
||||
utf8Buffer = Unpooled.copiedBuffer(utf8, CharsetUtil.UTF_8);
|
||||
}
|
||||
|
||||
@TearDown
|
||||
public void tearDown() {
|
||||
buffer.release();
|
||||
wrapped.release();
|
||||
asciiBuffer.release();
|
||||
utf8Buffer.release();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@ -165,4 +172,14 @@ public class
|
||||
wrapped.resetWriterIndex();
|
||||
ByteBufUtil.writeUtf8(wrapped, utf8Sequence);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public String decodeStringAscii() {
|
||||
return asciiBuffer.toString(CharsetUtil.US_ASCII);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public String decodeStringUtf8() {
|
||||
return utf8Buffer.toString(CharsetUtil.UTF_8);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user