Avoid allocations when wrapping byte[] and ByteBuffer arrays as ByteBuf (#8420)

Motivation:

Unpooled.wrap(byte[]...) and Unpooled.wrap(ByteBuffer...) currently
allocate/copy an intermediate ByteBuf ArrayList and array, which can be
avoided.

Modifications:

- Define new internal ByteWrapper interface and add a CompositeByteBuf
constructor which takes a ByteWrapper with an array of the type that it
wraps, and modify the appropriate Unpooled.wrap(...) methods to take
advantage of it
- Tidy up other constructors in CompositeByteBuf to remove duplication
and misleading len arg (which is really an end offset into provided
array)

Result:

Less allocation/copying when wrapping byte[] and ByteBuffer arrays,
tidier code.
This commit is contained in:
Nick Hill 2018-10-30 11:35:39 -07:00 committed by Norman Maurer
parent 52699bd6dd
commit 44cca1a26f
2 changed files with 106 additions and 90 deletions

View File

@ -53,65 +53,85 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
private boolean freed; private boolean freed;
public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents) { private CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, int initSize) {
super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY); super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY);
if (alloc == null) { if (alloc == null) {
throw new NullPointerException("alloc"); throw new NullPointerException("alloc");
} }
if (maxNumComponents < 2) {
throw new IllegalArgumentException(
"maxNumComponents: " + maxNumComponents + " (expected: >= 2)");
}
this.alloc = alloc; this.alloc = alloc;
this.direct = direct; this.direct = direct;
this.maxNumComponents = maxNumComponents; this.maxNumComponents = maxNumComponents;
components = newList(0, maxNumComponents); components = newList(initSize, maxNumComponents);
}
public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents) {
this(alloc, direct, maxNumComponents, 0);
} }
public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf... buffers) { public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf... buffers) {
this(alloc, direct, maxNumComponents, buffers, 0, buffers.length); this(alloc, direct, maxNumComponents, buffers, 0, buffers.length);
} }
CompositeByteBuf( CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents,
ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf[] buffers, int offset, int len) { ByteBuf[] buffers, int offset, int endOffset) {
super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY); this(alloc, direct, maxNumComponents, endOffset - offset);
if (alloc == null) {
throw new NullPointerException("alloc");
}
if (maxNumComponents < 2) {
throw new IllegalArgumentException(
"maxNumComponents: " + maxNumComponents + " (expected: >= 2)");
}
this.alloc = alloc; addComponents0(false, 0, buffers, offset, endOffset);
this.direct = direct;
this.maxNumComponents = maxNumComponents;
components = newList(len, maxNumComponents);
addComponents0(false, 0, buffers, offset, len);
consolidateIfNeeded(); consolidateIfNeeded();
setIndex(0, capacity()); setIndex(0, capacity());
} }
public CompositeByteBuf( public CompositeByteBuf(
ByteBufAllocator alloc, boolean direct, int maxNumComponents, Iterable<ByteBuf> buffers) { ByteBufAllocator alloc, boolean direct, int maxNumComponents, Iterable<ByteBuf> buffers) {
super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY); this(alloc, direct, maxNumComponents,
if (alloc == null) { buffers instanceof Collection ? ((Collection<ByteBuf>) buffers).size() : 0);
throw new NullPointerException("alloc");
}
if (maxNumComponents < 2) {
throw new IllegalArgumentException(
"maxNumComponents: " + maxNumComponents + " (expected: >= 2)");
}
int len = buffers instanceof Collection ? ((Collection<ByteBuf>) buffers).size() : 0;
this.alloc = alloc;
this.direct = direct;
this.maxNumComponents = maxNumComponents;
components = newList(len, maxNumComponents);
addComponents0(false, 0, buffers); addComponents0(false, 0, buffers);
consolidateIfNeeded(); consolidateIfNeeded();
setIndex(0, capacity()); setIndex(0, capacity());
} }
// support passing arrays of other types instead of having to copy to a ByteBuf[] first
interface ByteWrapper<T> {
ByteBuf wrap(T bytes);
boolean isEmpty(T bytes);
}
static final ByteWrapper<byte[]> BYTE_ARRAY_WRAPPER = new ByteWrapper<byte[]>() {
@Override
public ByteBuf wrap(byte[] bytes) {
return Unpooled.wrappedBuffer(bytes);
}
@Override
public boolean isEmpty(byte[] bytes) {
return bytes.length == 0;
}
};
static final ByteWrapper<ByteBuffer> BYTE_BUFFER_WRAPPER = new ByteWrapper<ByteBuffer>() {
@Override
public ByteBuf wrap(ByteBuffer bytes) {
return Unpooled.wrappedBuffer(bytes);
}
@Override
public boolean isEmpty(ByteBuffer bytes) {
return !bytes.hasRemaining();
}
};
<T> CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents,
ByteWrapper<T> wrapper, T[] buffers, int offset) {
this(alloc, direct, maxNumComponents, buffers.length - offset);
addComponents0(false, 0, wrapper, buffers, offset);
consolidateIfNeeded();
setIndex(0, capacity());
}
private static ComponentList newList(int initComponents, int maxNumComponents) { private static ComponentList newList(int initComponents, int maxNumComponents) {
int capacityGuess = Math.min(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS, maxNumComponents); int capacityGuess = Math.min(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS, maxNumComponents);
return new ComponentList(Math.max(initComponents, capacityGuess)); return new ComponentList(Math.max(initComponents, capacityGuess));
@ -301,14 +321,15 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
return this; return this;
} }
private int addComponents0(boolean increaseWriterIndex, int cIndex, ByteBuf[] buffers, int offset, int len) { private int addComponents0(boolean increaseWriterIndex, int cIndex,
ByteBuf[] buffers, int offset, int endOffset) {
checkNotNull(buffers, "buffers"); checkNotNull(buffers, "buffers");
int i = offset; int i = offset;
try { try {
checkComponentIndex(cIndex); checkComponentIndex(cIndex);
// No need for consolidation // No need for consolidation
while (i < len) { while (i < endOffset) {
// Increment i now to prepare for the next iteration and prevent a duplicate release (addComponent0 // Increment i now to prepare for the next iteration and prevent a duplicate release (addComponent0
// will release if an exception occurs, and we also release in the finally block here). // will release if an exception occurs, and we also release in the finally block here).
ByteBuf b = buffers[i++]; ByteBuf b = buffers[i++];
@ -323,7 +344,7 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
} }
return cIndex; return cIndex;
} finally { } finally {
for (; i < len; ++i) { for (; i < endOffset; ++i) {
ByteBuf b = buffers[i]; ByteBuf b = buffers[i];
if (b != null) { if (b != null) {
try { try {
@ -336,6 +357,28 @@ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements
} }
} }
private <T> int addComponents0(boolean increaseWriterIndex, int cIndex,
ByteWrapper<T> wrapper, T[] buffers, int offset) {
checkNotNull(buffers, "buffers");
checkComponentIndex(cIndex);
// No need for consolidation
for (int i = offset, len = buffers.length; i < len; i++) {
T b = buffers[i];
if (b == null) {
break;
}
if (!wrapper.isEmpty(b)) {
cIndex = addComponent0(increaseWriterIndex, cIndex, wrapper.wrap(b)) + 1;
int size = components.size();
if (cIndex > size) {
cIndex = size;
}
}
}
return cIndex;
}
/** /**
* Add the given {@link ByteBuf}s on the specific index * Add the given {@link ByteBuf}s on the specific index
* *

View File

@ -15,15 +15,14 @@
*/ */
package io.netty.buffer; package io.netty.buffer;
import io.netty.buffer.CompositeByteBuf.ByteWrapper;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; 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.Arrays; import java.util.Arrays;
import java.util.List;
/** /**
@ -262,38 +261,37 @@ public final class Unpooled {
return wrappedBuffer(buffers.length, buffers); return wrappedBuffer(buffers.length, buffers);
} }
static <T> ByteBuf wrappedBuffer(int maxNumComponents, ByteWrapper<T> wrapper, T[] array) {
switch (array.length) {
case 0:
break;
case 1:
if (!wrapper.isEmpty(array[0])) {
return wrapper.wrap(array[0]);
}
break;
default:
for (int i = 0, len = array.length; i < len; i++) {
T bytes = array[i];
if (bytes == null) {
return EMPTY_BUFFER;
}
if (!wrapper.isEmpty(bytes)) {
return new CompositeByteBuf(ALLOC, false, maxNumComponents, wrapper, array, i);
}
}
}
return EMPTY_BUFFER;
}
/** /**
* Creates a new big-endian composite buffer which wraps the specified * Creates a new big-endian composite buffer which wraps the specified
* arrays without copying them. A modification on the specified arrays' * arrays without copying them. A modification on the specified arrays'
* content will be visible to the returned buffer. * content will be visible to the returned buffer.
*/ */
public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays) { public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays) {
switch (arrays.length) { return wrappedBuffer(maxNumComponents, CompositeByteBuf.BYTE_ARRAY_WRAPPER, arrays);
case 0:
break;
case 1:
if (arrays[0].length != 0) {
return wrappedBuffer(arrays[0]);
}
break;
default:
// Get the list of the component, while guessing the byte order.
final List<ByteBuf> components = new ArrayList<ByteBuf>(arrays.length);
for (byte[] a: arrays) {
if (a == null) {
break;
}
if (a.length > 0) {
components.add(wrappedBuffer(a));
}
}
if (!components.isEmpty()) {
return new CompositeByteBuf(ALLOC, false, maxNumComponents, components);
}
}
return EMPTY_BUFFER;
} }
/** /**
@ -336,32 +334,7 @@ public final class Unpooled {
* specified buffers will be visible to the returned buffer. * specified buffers will be visible to the returned buffer.
*/ */
public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers) { public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers) {
switch (buffers.length) { return wrappedBuffer(maxNumComponents, CompositeByteBuf.BYTE_BUFFER_WRAPPER, buffers);
case 0:
break;
case 1:
if (buffers[0].hasRemaining()) {
return wrappedBuffer(buffers[0].order(BIG_ENDIAN));
}
break;
default:
// Get the list of the component, while guessing the byte order.
final List<ByteBuf> components = new ArrayList<ByteBuf>(buffers.length);
for (ByteBuffer b: buffers) {
if (b == null) {
break;
}
if (b.remaining() > 0) {
components.add(wrappedBuffer(b.order(BIG_ENDIAN)));
}
}
if (!components.isEmpty()) {
return new CompositeByteBuf(ALLOC, false, maxNumComponents, components);
}
}
return EMPTY_BUFFER;
} }
/** /**