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:
parent
06f3574e46
commit
f164759ea3
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user