I need to get a view of a ByteBuffer b
using the same object IntBuffer viewBuffer
without having to instantiate a new object every time the reference to the ByteBuffer
changes.
// An external ByteBuffer
ByteBuffer b1 = ByteBuffer.allocateDirect(1024 * 4).order(ByteOrder.LITTLE_ENDIAN);
// get a Int view over the bytebuffer
IntBuffer viewBuffer1 = b1.asIntBuffer() // this creates an object
// Another external ByteBuffer
ByteBuffer b2 = ByteBuffer.allocateDirect(1024 * 4).order(ByteOrder.LITTLE_ENDIAN);
// get an Int view offer the new bytebuffer
IntBuffer viewBuffer2 = b1.asIntBuffer() // this creates another object
Here is the implementation of asIntBuffer()
in DirectByteBuffer
where a new object is created every time we call the method.
public IntBuffer asIntBuffer() {
int off = this.position();
int lim = this.limit();
assert off <= lim;
int rem = off <= lim ? lim - off : 0;
int size = rem >> 2;
if (!UNALIGNED && (this.address + (long)off) % 4L != 0L) {
return (IntBuffer)(this.bigEndian ? new ByteBufferAsIntBufferB(this, -1, 0, size, size, this.address + (long)off) : new ByteBufferAsIntBufferL(this, -1, 0, size, size, this.address + (long)off));
} else {
return (IntBuffer)(this.nativeByteOrder ? new DirectIntBufferU(this, -1, 0, size, size, off) : new DirectIntBufferS(this, -1, 0, size, size, off));
}
}
We work on a high performance application requiring copy-free, allocation-free and fast processing and since IntBuffer
does not provide a wrap
method, I tried to implement my own view class that wraps different ByteBuffers
, the performance in terms of processing time was not the same as Java's IntBuffer
. Indeed, I could see in the disassembled code that operations over an IntBuffer
are auto-vectorized but operations over the ByteBuffer
are not.
I also tried creating a class that extends IntBuffer
in order to add a method wrap(ByteBuffer bb)
, but not only the package java.nio
is private, the field ByteBuffer bb
used internally is final
.
See how an IntBuffer
is instantiated. I do not see why they did not provide a wrap
method here or why the field bb
is made final. I mean, using the same constructor code we could easily have a wrap method that just points to a different ByteBuffer bb
without the unnecessary object creation.
class ByteBufferAsIntBufferB extends IntBuffer {
protected final ByteBuffer bb;
ByteBufferAsIntBufferB(ByteBuffer bb) {
super(-1, 0, bb.remaining() >> 2, bb.remaining() >> 2);
this.bb = bb;
int cap = this.capacity();
this.limit(cap);
int pos = this.position();
assert pos <= cap;
this.address = bb.address;
}
My question is how can I make operations over Ints
in a ByteBuffer
run as fast as the java IntBuffer
, i.e. get auto-vectorized by the JIT compiler.
Edit: Here is my custom class IntViewBuffer where one object can be reused.
public class IntViewBuffer{
private ByteBuffer bb;
private int offset;
private int limit;
private int INT_BYTE_SIZE = 4;
public IntViewBuffer(ByteBuffer bb, int offset, int limit) {
this.wrap(bb, offset, limit);
}
public void wrap(ByteBuffer bb, int offset, int limit){
this.bb = bb;
assert (limit <= bb.capacity() && offset <= limit);
this.offset = offset;
this.limit = limit;
}
private int getByteIndex(int index){
int byteIndex = (offset + index * INT_BYTE_SIZE);
assert(byteIndex <= limit);
return byteIndex;
}
public void put(int index, int value){
this.bb.putInt(getByteIndex(index), value);
}
public int get(int index){
return this.bb.getInt(getByteIndex(index));
}
}
With this class I can reuse my view buffer but operations will not be auto-vectorized (by checking the assembly code, I did not find the AVX-512
SIMD instructions that usually appear with IntBuffer
).
// An external ByteBuffer
ByteBuffer b1 = ByteBuffer.allocateDirect(1024 * 4).order(ByteOrder.LITTLE_ENDIAN);
// get a Int view over the bytebuffer
IntViewBuffer view = new IntViewBuffer(b1, 0, b1.capacity());
// Another external ByteBuffer
ByteBuffer b2 = ByteBuffer.allocateDirect(1024 * 4).order(ByteOrder.LITTLE_ENDIAN);
// reuse my object to wrap the new bytebuffer
view.wrap(b2, 0, b2.capacity());