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:
Norman Maurer 2015-10-13 11:34:29 +02:00
parent 4aa19a09bd
commit 2aef4a504f
4 changed files with 83 additions and 20 deletions

View File

@ -947,20 +947,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

View File

@ -23,6 +23,7 @@ import io.netty.util.ByteString;
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.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
@ -47,7 +48,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];
@ -133,6 +141,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);
}
/**
@ -579,10 +590,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()) {
@ -595,7 +637,6 @@ public final class ByteBufUtil {
} catch (CharacterCodingException x) {
throw new IllegalStateException(x);
}
return dst.flip().toString();
}
/**

View File

@ -27,6 +27,8 @@ import java.util.Random;
import org.junit.Assert;
import org.junit.Test;
import java.nio.charset.Charset;
public class ByteBufUtilTest {
@Test
public void equalsBufferSubsections() {
@ -156,4 +158,20 @@ public class ByteBufUtilTest {
private static void assertWrapped(ByteBuf buf) {
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();
}
}

View File

@ -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);
}
}