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

View File

@ -22,6 +22,7 @@ import java.nio.ByteOrder;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; 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. * not try to slice the given {@link ByteBuf}s to reduce GC-Pressure.
*/ */
public static ByteBuf unmodifiableBuffer(ByteBuf... buffers) { 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() { private Unpooled() {