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 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user