0

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()); 

PhiloJunkie
  • 1,111
  • 4
  • 13
  • 27
  • The question lacks an [MCVE](https://stackoverflow.com/help/minimal-reproducible-example) – apangin Feb 01 '23 at 12:34
  • @apangin I am not sure an MCVE would be useful but I will try to add one. Thanks! – PhiloJunkie Feb 01 '23 at 14:13
  • @apangin I hope that my edit made things clearer. – PhiloJunkie Feb 01 '23 at 16:02
  • @matt I have hundreds (up to millions) of ByteBuffers that I need to process in streaming using the same logic (by the same object) hence why I want to reuse only one IntBuffer (view) that can point to these byte buffers in order to avoid the garbage collection resulting from creating millions of views. – PhiloJunkie Feb 01 '23 at 16:09
  • @matt unfortunately, the actual implementations of `IntBuffer` do not allow switching the backing data, therefore my question about the possibility of a `wrap` method. In our application, the `ByteBuffers` are stored off-heap and managed directly through the `LMDB` data store. We are reading directly from memory so the real challenge is how to make processing faster without having to allocate a new object every time a different `ByteBuffer` is read by LMDB. I hope this made my question clearer, I will try to also edit my post. – PhiloJunkie Feb 01 '23 at 17:05
  • It's still not clear. Look at your first example. What is the point of ByteBuffer b2? Why include the code for "asIntBuffer" it doesn't help your question. Why include the code for "ByteBufferAsIntBufferB" it doesnt' help your question. Your view class just calls `bb.getInt` and `bb.putInt` instead of using an IntBuffer view. New Objects are cheap, so you appear to be prematurely optimizing. If not, create a bench mark and say what you want to be improved. Maybe use a profiler. – matt Feb 02 '23 at 08:31
  • 1
    If you want to imitate the IntBuffer for the methods you need. I am guessing get and put? You should look at the specific implementation you're using. Then look at the get put methods. They're not so simple as 'bb.getInt` they're using Bits or Unsafe. – matt Feb 02 '23 at 08:39
  • 2
    There is possibly a really good quesiton here but it is buried in the idea of "Re-implementing the IntBuffer to avoid new object creation." I think you need to narrow it down to something a bit more tractable so an answer author can reproduce the problem. Ie An example script that uses an IntBuffer and performs well to a specific spec. Then can the IntBuffer methods be replaced with ByteBuffer calls directly. – matt Feb 02 '23 at 09:50
  • @matt I totally understand your comments, my thing is my problem is part of a huge class so I couldn't really pick the right pieces of code to share! thank you, I will update my question. – PhiloJunkie Feb 02 '23 at 10:02

0 Answers0