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
|
@Override
|
||||||
public String toString(int index, int length, Charset charset) {
|
public String toString(int index, int length, Charset charset) {
|
||||||
if (length == 0) {
|
return ByteBufUtil.decodeString(this, index, length, charset);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.buffer;
|
|||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
import io.netty.util.Recycler;
|
import io.netty.util.Recycler;
|
||||||
import io.netty.util.Recycler.Handle;
|
import io.netty.util.Recycler.Handle;
|
||||||
|
import io.netty.util.concurrent.FastThreadLocal;
|
||||||
import io.netty.util.internal.ObjectUtil;
|
import io.netty.util.internal.ObjectUtil;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
@ -42,7 +43,14 @@ import java.util.Locale;
|
|||||||
public final class ByteBufUtil {
|
public final class ByteBufUtil {
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ByteBufUtil.class);
|
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 char[] HEXDUMP_TABLE = new char[256 * 4];
|
||||||
private static final String NEWLINE = StringUtil.NEWLINE;
|
private static final String NEWLINE = StringUtil.NEWLINE;
|
||||||
private static final String[] BYTE2HEX = new String[256];
|
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);
|
THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", 64 * 1024);
|
||||||
logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);
|
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 CharsetDecoder decoder = CharsetUtil.getDecoder(charset);
|
||||||
final CharBuffer dst = CharBuffer.allocate(
|
final int maxLength = (int) ((double) len * decoder.maxCharsPerByte());
|
||||||
(int) ((double) src.remaining() * 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 {
|
try {
|
||||||
CoderResult cr = decoder.decode(src, dst, true);
|
CoderResult cr = decoder.decode(src, dst, true);
|
||||||
if (!cr.isUnderflow()) {
|
if (!cr.isUnderflow()) {
|
||||||
@ -535,7 +577,6 @@ public final class ByteBufUtil {
|
|||||||
} catch (CharacterCodingException x) {
|
} catch (CharacterCodingException x) {
|
||||||
throw new IllegalStateException(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.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
public class ByteBufUtilTest {
|
public class ByteBufUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -73,4 +75,20 @@ public class ByteBufUtilTest {
|
|||||||
private static void assertWrapped(ByteBuf buf) {
|
private static void assertWrapped(ByteBuf buf) {
|
||||||
Assert.assertTrue(buf instanceof WrappedByteBuf);
|
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)
|
@State(Scope.Benchmark)
|
||||||
@Warmup(iterations = 10)
|
@Warmup(iterations = 5)
|
||||||
@Measurement(iterations = 25)
|
@Measurement(iterations = 10)
|
||||||
public class
|
public class
|
||||||
ByteBufUtilBenchmark extends AbstractMicrobenchmark {
|
ByteBufUtilBenchmark extends AbstractMicrobenchmark {
|
||||||
private ByteBuf buffer;
|
private ByteBuf buffer;
|
||||||
private ByteBuf wrapped;
|
private ByteBuf wrapped;
|
||||||
|
private ByteBuf asciiBuffer;
|
||||||
|
private ByteBuf utf8Buffer;
|
||||||
|
|
||||||
private StringBuilder asciiSequence;
|
private StringBuilder asciiSequence;
|
||||||
private String ascii;
|
private String ascii;
|
||||||
@ -62,12 +64,17 @@ public class
|
|||||||
}
|
}
|
||||||
utf8 = utf8Sequence.toString();
|
utf8 = utf8Sequence.toString();
|
||||||
asciiSequence = utf8Sequence;
|
asciiSequence = utf8Sequence;
|
||||||
|
|
||||||
|
asciiBuffer = Unpooled.copiedBuffer(ascii, CharsetUtil.US_ASCII);
|
||||||
|
utf8Buffer = Unpooled.copiedBuffer(utf8, CharsetUtil.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TearDown
|
@TearDown
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
buffer.release();
|
buffer.release();
|
||||||
wrapped.release();
|
wrapped.release();
|
||||||
|
asciiBuffer.release();
|
||||||
|
utf8Buffer.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@ -165,4 +172,14 @@ public class
|
|||||||
wrapped.resetWriterIndex();
|
wrapped.resetWriterIndex();
|
||||||
ByteBufUtil.writeUtf8(wrapped, utf8Sequence);
|
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