Support composite buffer creation without array alloc and copy

Motivation:

Unpooled.unmodifiableBuffer() is currently used to efficiently write
arrays of ByteBufs via FixedCompositeByteBuf, but involves an allocation
and content-copy of the provided ByteBuf array which in many (most?)
cases shouldn't be necessary.

Modifications:

Modify the internal FixedCompositeByteBuf class to support wrapping the
provided ByteBuf array directly. Control this behaviour with a
constructor flag and expose the "unsafe" version via a new
Unpooled.wrappedUnmodifiableBuffer(ByteBuf...) method.

Result:

Less garbage on IO paths. I would guess pretty much all existing usage
of unmodifiableBuffer() could use the copy-free version but assume it's
not safe to change its default behaviour.
This commit is contained in:
nickhill 2018-06-20 14:12:44 -07:00 committed by Norman Maurer
parent 06f3574e46
commit f164759ea3
2 changed files with 36 additions and 23 deletions

View File

@ -39,7 +39,7 @@ final class FixedCompositeByteBuf extends AbstractReferenceCountedByteBuf {
private final int capacity;
private final ByteBufAllocator allocator;
private final ByteOrder order;
private final Object[] buffers;
private final ByteBuf[] buffers;
private final boolean direct;
FixedCompositeByteBuf(ByteBufAllocator allocator, ByteBuf... buffers) {
@ -52,8 +52,7 @@ final class FixedCompositeByteBuf extends AbstractReferenceCountedByteBuf {
direct = false;
} else {
ByteBuf b = buffers[0];
this.buffers = new Object[buffers.length];
this.buffers[0] = b;
this.buffers = buffers;
boolean direct = true;
int nioBufferCount = b.nioBufferCount();
int capacity = b.readableBytes();
@ -68,7 +67,6 @@ final class FixedCompositeByteBuf extends AbstractReferenceCountedByteBuf {
if (!b.isDirect()) {
direct = false;
}
this.buffers[i] = b;
}
this.nioBufferCount = nioBufferCount;
this.capacity = capacity;
@ -232,20 +230,14 @@ final class FixedCompositeByteBuf extends AbstractReferenceCountedByteBuf {
int readable = 0;
for (int i = 0 ; i < buffers.length; i++) {
Component comp = null;
ByteBuf b;
Object obj = buffers[i];
boolean isBuffer;
if (obj instanceof ByteBuf) {
b = (ByteBuf) obj;
isBuffer = true;
} else {
comp = (Component) obj;
ByteBuf b = buffers[i];
if (b instanceof Component) {
comp = (Component) b;
b = comp.buf;
isBuffer = false;
}
readable += b.readableBytes();
if (index < readable) {
if (isBuffer) {
if (comp == null) {
// Create a new component and store it in the array so it not create a new object
// on the next access.
comp = new Component(i, readable - b.readableBytes(), b);
@ -261,11 +253,8 @@ final class FixedCompositeByteBuf extends AbstractReferenceCountedByteBuf {
* Return the {@link ByteBuf} stored at the given index of the array.
*/
private ByteBuf buffer(int i) {
Object obj = buffers[i];
if (obj instanceof ByteBuf) {
return (ByteBuf) obj;
}
return ((Component) obj).buf;
ByteBuf b = buffers[i];
return b instanceof Component ? ((Component) b).buf : b;
}
@Override
@ -684,17 +673,16 @@ final class FixedCompositeByteBuf extends AbstractReferenceCountedByteBuf {
return result + ", components=" + buffers.length + ')';
}
private static final class Component {
private static final class Component extends WrappedByteBuf {
private final int index;
private final int offset;
private final ByteBuf buf;
private final int endOffset;
Component(int index, int offset, ByteBuf buf) {
super(buf);
this.index = index;
this.offset = offset;
endOffset = offset + buf.readableBytes();
this.buf = buf;
}
}
}

View File

@ -22,6 +22,7 @@ import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -883,7 +884,31 @@ public final class Unpooled {
* not try to slice the given {@link ByteBuf}s to reduce GC-Pressure.
*/
public static ByteBuf unmodifiableBuffer(ByteBuf... buffers) {
return new FixedCompositeByteBuf(ALLOC, buffers);
return wrappedUnmodifiableBuffer(true, buffers);
}
/**
* Wrap the given {@link ByteBuf}s in an unmodifiable {@link ByteBuf}. Be aware the returned {@link ByteBuf} will
* not try to slice the given {@link ByteBuf}s to reduce GC-Pressure.
*
* The returned {@link ByteBuf} wraps the provided array directly, and so should not be subsequently modified.
*/
public static ByteBuf wrappedUnmodifiableBuffer(ByteBuf... buffers) {
return wrappedUnmodifiableBuffer(false, buffers);
}
private static ByteBuf wrappedUnmodifiableBuffer(boolean copy, ByteBuf... buffers) {
switch (buffers.length) {
case 0:
return EMPTY_BUFFER;
case 1:
return buffers[0].asReadOnly();
default:
if (copy) {
buffers = Arrays.copyOf(buffers, buffers.length, ByteBuf[].class);
}
return new FixedCompositeByteBuf(ALLOC, buffers);
}
}
private Unpooled() {