[#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:
parent
42e6b8fa86
commit
30dc1c1fa4
@ -450,39 +450,46 @@ public final class ByteBufUtil {
|
|||||||
final int len = seq.length();
|
final int len = seq.length();
|
||||||
final int maxSize = len * 3;
|
final int maxSize = len * 3;
|
||||||
buf.ensureWritable(maxSize);
|
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.
|
for (;;) {
|
||||||
// This is possible as we called ensureWritable(...) before.
|
if (buf instanceof AbstractByteBuf) {
|
||||||
for (int i = 0; i < len; i++) {
|
return writeUtf8((AbstractByteBuf) buf, seq, len);
|
||||||
char c = seq.charAt(i);
|
} else if (buf instanceof WrappedByteBuf) {
|
||||||
if (c < 0x80) {
|
// Unwrap as the wrapped buffer may be an AbstractByteBuf and so we can use fast-path.
|
||||||
buffer._setByte(writerIndex++, (byte) c);
|
buf = buf.unwrap();
|
||||||
} else if (c < 0x800) {
|
} else {
|
||||||
buffer._setByte(writerIndex++, (byte) (0xc0 | (c >> 6)));
|
byte[] bytes = seq.toString().getBytes(CharsetUtil.UTF_8);
|
||||||
buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f)));
|
buf.writeBytes(bytes);
|
||||||
} else {
|
return bytes.length;
|
||||||
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;
|
|
||||||
} 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
|
* Encode a {@link CharSequence} in <a href="http://en.wikipedia.org/wiki/ASCII">ASCII</a> and write it
|
||||||
* to a {@link ByteBuf}.
|
* to a {@link ByteBuf}.
|
||||||
@ -502,26 +509,35 @@ public final class ByteBufUtil {
|
|||||||
if (seq instanceof AsciiString) {
|
if (seq instanceof AsciiString) {
|
||||||
AsciiString asciiString = (AsciiString) seq;
|
AsciiString asciiString = (AsciiString) seq;
|
||||||
buf.writeBytes(asciiString.array(), asciiString.arrayOffset(), asciiString.length());
|
buf.writeBytes(asciiString.array(), asciiString.arrayOffset(), asciiString.length());
|
||||||
} else 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));
|
|
||||||
}
|
|
||||||
// update the writerIndex without any extra checks for performance reasons
|
|
||||||
buffer.writerIndex = writerIndex;
|
|
||||||
} else {
|
} else {
|
||||||
// Maybe we could also check if we can unwrap() to access the wrapped buffer which
|
for (;;) {
|
||||||
// may be an AbstractByteBuf. But this may be overkill so let us keep it simple for now.
|
if (buf instanceof AbstractByteBuf) {
|
||||||
buf.writeBytes(seq.toString().getBytes(CharsetUtil.US_ASCII));
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return len;
|
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
|
* Encode the given {@link CharBuffer} using the given {@link Charset} into a new {@link ByteBuf} which
|
||||||
* is allocated via the {@link ByteBufAllocator}.
|
* is allocated via the {@link ByteBufAllocator}.
|
||||||
|
@ -28,7 +28,14 @@ import java.nio.channels.GatheringByteChannel;
|
|||||||
import java.nio.channels.ScatteringByteChannel;
|
import java.nio.channels.ScatteringByteChannel;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
public class WrappedByteBuf extends ByteBuf {
|
/**
|
||||||
|
* 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;
|
protected final ByteBuf buf;
|
||||||
|
|
||||||
@ -40,17 +47,17 @@ public class WrappedByteBuf extends ByteBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasMemoryAddress() {
|
public final boolean hasMemoryAddress() {
|
||||||
return buf.hasMemoryAddress();
|
return buf.hasMemoryAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long memoryAddress() {
|
public final long memoryAddress() {
|
||||||
return buf.memoryAddress();
|
return buf.memoryAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int capacity() {
|
public final int capacity() {
|
||||||
return buf.capacity();
|
return buf.capacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,17 +68,17 @@ public class WrappedByteBuf extends ByteBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int maxCapacity() {
|
public final int maxCapacity() {
|
||||||
return buf.maxCapacity();
|
return buf.maxCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBufAllocator alloc() {
|
public final ByteBufAllocator alloc() {
|
||||||
return buf.alloc();
|
return buf.alloc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteOrder order() {
|
public final ByteOrder order() {
|
||||||
return buf.order();
|
return buf.order();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,33 +88,33 @@ public class WrappedByteBuf extends ByteBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf unwrap() {
|
public final ByteBuf unwrap() {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirect() {
|
public final boolean isDirect() {
|
||||||
return buf.isDirect();
|
return buf.isDirect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readerIndex() {
|
public final int readerIndex() {
|
||||||
return buf.readerIndex();
|
return buf.readerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf readerIndex(int readerIndex) {
|
public final ByteBuf readerIndex(int readerIndex) {
|
||||||
buf.readerIndex(readerIndex);
|
buf.readerIndex(readerIndex);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int writerIndex() {
|
public final int writerIndex() {
|
||||||
return buf.writerIndex();
|
return buf.writerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf writerIndex(int writerIndex) {
|
public final ByteBuf writerIndex(int writerIndex) {
|
||||||
buf.writerIndex(writerIndex);
|
buf.writerIndex(writerIndex);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -119,56 +126,56 @@ public class WrappedByteBuf extends ByteBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readableBytes() {
|
public final int readableBytes() {
|
||||||
return buf.readableBytes();
|
return buf.readableBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int writableBytes() {
|
public final int writableBytes() {
|
||||||
return buf.writableBytes();
|
return buf.writableBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int maxWritableBytes() {
|
public final int maxWritableBytes() {
|
||||||
return buf.maxWritableBytes();
|
return buf.maxWritableBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReadable() {
|
public final boolean isReadable() {
|
||||||
return buf.isReadable();
|
return buf.isReadable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWritable() {
|
public final boolean isWritable() {
|
||||||
return buf.isWritable();
|
return buf.isWritable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf clear() {
|
public final ByteBuf clear() {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf markReaderIndex() {
|
public final ByteBuf markReaderIndex() {
|
||||||
buf.markReaderIndex();
|
buf.markReaderIndex();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf resetReaderIndex() {
|
public final ByteBuf resetReaderIndex() {
|
||||||
buf.resetReaderIndex();
|
buf.resetReaderIndex();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf markWriterIndex() {
|
public final ByteBuf markWriterIndex() {
|
||||||
buf.markWriterIndex();
|
buf.markWriterIndex();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuf resetWriterIndex() {
|
public final ByteBuf resetWriterIndex() {
|
||||||
buf.resetWriterIndex();
|
buf.resetWriterIndex();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -814,17 +821,17 @@ public class WrappedByteBuf extends ByteBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReadable(int size) {
|
public final boolean isReadable(int size) {
|
||||||
return buf.isReadable(size);
|
return buf.isReadable(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWritable(int size) {
|
public final boolean isWritable(int size) {
|
||||||
return buf.isWritable(size);
|
return buf.isWritable(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int refCnt() {
|
public final int refCnt() {
|
||||||
return buf.refCnt();
|
return buf.refCnt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +105,19 @@ public class ByteBufUtilTest {
|
|||||||
Assert.assertEquals(buf, buf2);
|
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
|
@Test
|
||||||
public void testWriteUtf8() {
|
public void testWriteUtf8() {
|
||||||
String usAscii = "Some UTF-8 like äÄ∏ŒŒ";
|
String usAscii = "Some UTF-8 like äÄ∏ŒŒ";
|
||||||
@ -126,4 +139,21 @@ public class ByteBufUtilTest {
|
|||||||
|
|
||||||
Assert.assertEquals(buf, buf2);
|
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) {
|
||||||
|
assertTrue(buf instanceof WrappedByteBuf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user