[#4313] ByteBufUtil.writeUtf8 should use fast-path for WrappedByteBuf

Motivation:

ByteBufUtil.writeUtf8(...) / writeUsAscii(...) can use a fast-path when writing into AbstractByteBuf. We should try to unwrap WrappedByteBuf implementations so
we are able to do the same on wrapped AbstractByteBuf instances.

Modifications:

- Try to unwrap WrappedByteBuf to use the fast-path

Result:

Faster writing of utf8 and usascii for WrappedByteBuf instances.
This commit is contained in:
Norman Maurer 2015-10-08 13:54:48 +02:00
parent b79714ab5a
commit d6a00d0642
3 changed files with 119 additions and 68 deletions

View File

@ -395,39 +395,46 @@ public final class ByteBufUtil {
final int len = seq.length();
final int maxSize = len * 3;
buf.ensureWritable(maxSize);
if (buf instanceof AbstractByteBuf) {
// Fast-Path
AbstractByteBuf buffer = (AbstractByteBuf) buf;
int oldWriterIndex = buffer.writerIndex;
int writerIndex = oldWriterIndex;
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++) {
char c = seq.charAt(i);
if (c < 0x80) {
buffer._setByte(writerIndex++, (byte) c);
} else if (c < 0x800) {
buffer._setByte(writerIndex++, (byte) (0xc0 | (c >> 6)));
buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f)));
} else {
buffer._setByte(writerIndex++, (byte) (0xe0 | (c >> 12)));
buffer._setByte(writerIndex++, (byte) (0x80 | ((c >> 6) & 0x3f)));
buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f)));
}
for (;;) {
if (buf instanceof AbstractByteBuf) {
return writeUtf8((AbstractByteBuf) buf, seq, len);
} else if (buf instanceof WrappedByteBuf) {
// Unwrap as the wrapped buffer may be an AbstractByteBuf and so we can use fast-path.
buf = buf.unwrap();
} else {
byte[] bytes = seq.toString().getBytes(CharsetUtil.UTF_8);
buf.writeBytes(bytes);
return bytes.length;
}
// update the writerIndex without any extra checks for performance reasons
buffer.writerIndex = writerIndex;
return writerIndex - oldWriterIndex;
} else {
// Maybe we could also check if we can unwrap() to access the wrapped buffer which
// may be an AbstractByteBuf. But this may be overkill so let us keep it simple for now.
byte[] bytes = seq.toString().getBytes(CharsetUtil.UTF_8);
buf.writeBytes(bytes);
return bytes.length;
}
}
// Fast-Path implementation
private static int writeUtf8(AbstractByteBuf buffer, CharSequence seq, int len) {
int oldWriterIndex = buffer.writerIndex;
int writerIndex = oldWriterIndex;
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++) {
char c = seq.charAt(i);
if (c < 0x80) {
buffer._setByte(writerIndex++, (byte) c);
} else if (c < 0x800) {
buffer._setByte(writerIndex++, (byte) (0xc0 | (c >> 6)));
buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f)));
} else {
buffer._setByte(writerIndex++, (byte) (0xe0 | (c >> 12)));
buffer._setByte(writerIndex++, (byte) (0x80 | ((c >> 6) & 0x3f)));
buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f)));
}
}
// update the writerIndex without any extra checks for performance reasons
buffer.writerIndex = writerIndex;
return writerIndex - oldWriterIndex;
}
/**
* Encode a {@link CharSequence} in <a href="http://en.wikipedia.org/wiki/ASCII">ASCII</a> and write it
* to a {@link ByteBuf}.
@ -444,26 +451,33 @@ public final class ByteBufUtil {
// ASCII uses 1 byte per char
final int len = seq.length();
buf.ensureWritable(len);
if (buf instanceof AbstractByteBuf) {
// Fast-Path
AbstractByteBuf buffer = (AbstractByteBuf) buf;
int writerIndex = buffer.writerIndex;
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++) {
buffer._setByte(writerIndex++, (byte) seq.charAt(i));
for (;;) {
if (buf instanceof AbstractByteBuf) {
writeAscii((AbstractByteBuf) buf, seq, len);
break;
} else if (buf instanceof WrappedByteBuf) {
// Unwrap as the wrapped buffer may be an AbstractByteBuf and so we can use fast-path.
buf = buf.unwrap();
} else {
buf.writeBytes(seq.toString().getBytes(CharsetUtil.US_ASCII));
}
// update the writerIndex without any extra checks for performance reasons
buffer.writerIndex = writerIndex;
} else {
// Maybe we could also check if we can unwrap() to access the wrapped buffer which
// may be an AbstractByteBuf. But this may be overkill so let us keep it simple for now.
buf.writeBytes(seq.toString().getBytes(CharsetUtil.US_ASCII));
}
return len;
}
// Fast-Path implementation
private static void writeAscii(AbstractByteBuf buffer, CharSequence seq, int len) {
int writerIndex = buffer.writerIndex;
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++) {
buffer._setByte(writerIndex++, (byte) seq.charAt(i));
}
// update the writerIndex without any extra checks for performance reasons
buffer.writerIndex = writerIndex;
}
/**
* Encode the given {@link CharBuffer} using the given {@link Charset} into a new {@link ByteBuf} which
* is allocated via the {@link ByteBufAllocator}.

View File

@ -27,6 +27,13 @@ import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.charset.Charset;
/**
* Wraps another {@link ByteBuf}.
*
* It's important that the {@link #readerIndex()} and {@link #writerIndex()} will not do any adjustments on the
* indices on the fly because of internal optimizations made by {@link ByteBufUtil#writeAscii(ByteBuf, CharSequence)}
* and {@link ByteBufUtil#writeUtf8(ByteBuf, CharSequence)}.
*/
class WrappedByteBuf extends ByteBuf {
protected final ByteBuf buf;
@ -39,17 +46,17 @@ class WrappedByteBuf extends ByteBuf {
}
@Override
public boolean hasMemoryAddress() {
public final boolean hasMemoryAddress() {
return buf.hasMemoryAddress();
}
@Override
public long memoryAddress() {
public final long memoryAddress() {
return buf.memoryAddress();
}
@Override
public int capacity() {
public final int capacity() {
return buf.capacity();
}
@ -60,17 +67,17 @@ class WrappedByteBuf extends ByteBuf {
}
@Override
public int maxCapacity() {
public final int maxCapacity() {
return buf.maxCapacity();
}
@Override
public ByteBufAllocator alloc() {
public final ByteBufAllocator alloc() {
return buf.alloc();
}
@Override
public ByteOrder order() {
public final ByteOrder order() {
return buf.order();
}
@ -80,33 +87,33 @@ class WrappedByteBuf extends ByteBuf {
}
@Override
public ByteBuf unwrap() {
public final ByteBuf unwrap() {
return buf;
}
@Override
public boolean isDirect() {
public final boolean isDirect() {
return buf.isDirect();
}
@Override
public int readerIndex() {
public final int readerIndex() {
return buf.readerIndex();
}
@Override
public ByteBuf readerIndex(int readerIndex) {
public final ByteBuf readerIndex(int readerIndex) {
buf.readerIndex(readerIndex);
return this;
}
@Override
public int writerIndex() {
public final int writerIndex() {
return buf.writerIndex();
}
@Override
public ByteBuf writerIndex(int writerIndex) {
public final ByteBuf writerIndex(int writerIndex) {
buf.writerIndex(writerIndex);
return this;
}
@ -118,56 +125,56 @@ class WrappedByteBuf extends ByteBuf {
}
@Override
public int readableBytes() {
public final int readableBytes() {
return buf.readableBytes();
}
@Override
public int writableBytes() {
public final int writableBytes() {
return buf.writableBytes();
}
@Override
public int maxWritableBytes() {
public final int maxWritableBytes() {
return buf.maxWritableBytes();
}
@Override
public boolean isReadable() {
public final boolean isReadable() {
return buf.isReadable();
}
@Override
public boolean isWritable() {
public final boolean isWritable() {
return buf.isWritable();
}
@Override
public ByteBuf clear() {
public final ByteBuf clear() {
buf.clear();
return this;
}
@Override
public ByteBuf markReaderIndex() {
public final ByteBuf markReaderIndex() {
buf.markReaderIndex();
return this;
}
@Override
public ByteBuf resetReaderIndex() {
public final ByteBuf resetReaderIndex() {
buf.resetReaderIndex();
return this;
}
@Override
public ByteBuf markWriterIndex() {
public final ByteBuf markWriterIndex() {
buf.markWriterIndex();
return this;
}
@Override
public ByteBuf resetWriterIndex() {
public final ByteBuf resetWriterIndex() {
buf.resetWriterIndex();
return this;
}
@ -801,17 +808,17 @@ class WrappedByteBuf extends ByteBuf {
}
@Override
public boolean isReadable(int size) {
public final boolean isReadable(int size) {
return buf.isReadable(size);
}
@Override
public boolean isWritable(int size) {
public final boolean isWritable(int size) {
return buf.isWritable(size);
}
@Override
public int refCnt() {
public final int refCnt() {
return buf.refCnt();
}

View File

@ -33,6 +33,19 @@ public class ByteBufUtilTest {
Assert.assertEquals(buf, buf2);
}
@Test
public void testWriteUsAsciiWrapped() {
String usAscii = "NettyRocks";
ByteBuf buf = Unpooled.unreleasableBuffer(ReferenceCountUtil.releaseLater(Unpooled.buffer(16)));
assertWrapped(buf);
buf.writeBytes(usAscii.getBytes(CharsetUtil.US_ASCII));
ByteBuf buf2 = Unpooled.unreleasableBuffer(ReferenceCountUtil.releaseLater(Unpooled.buffer(16)));
assertWrapped(buf2);
ByteBufUtil.writeAscii(buf2, usAscii);
Assert.assertEquals(buf, buf2);
}
@Test
public void testWriteUtf8() {
String usAscii = "Some UTF-8 like äÄ∏ŒŒ";
@ -43,4 +56,21 @@ public class ByteBufUtilTest {
Assert.assertEquals(buf, buf2);
}
@Test
public void testWriteUtf8Wrapped() {
String usAscii = "Some UTF-8 like äÄ∏ŒŒ";
ByteBuf buf = Unpooled.unreleasableBuffer(ReferenceCountUtil.releaseLater(Unpooled.buffer(16)));
assertWrapped(buf);
buf.writeBytes(usAscii.getBytes(CharsetUtil.UTF_8));
ByteBuf buf2 = Unpooled.unreleasableBuffer(ReferenceCountUtil.releaseLater(Unpooled.buffer(16)));
assertWrapped(buf2);
ByteBufUtil.writeUtf8(buf2, usAscii);
Assert.assertEquals(buf, buf2);
}
private static void assertWrapped(ByteBuf buf) {
Assert.assertTrue(buf instanceof WrappedByteBuf);
}
}