Oh, and there is MethodHandles.byteBufferViewVarHandle 
<https://docs.oracle.com/javase/10/docs/api/java/lang/invoke/MethodHandles.html#byteBufferViewVarHandle(java.lang.Class,java.nio.ByteOrder)>
 
if you (for some reason) want to do the same but keep ByteBuffers around.

On Tuesday, August 7, 2018 at 9:41:01 PM UTC-7, Gil Tene wrote:
>
> *IF* you can use post-java-8 stuff, VarHandles may have a more systemic 
> and intentional/explicit answer for expressing what you are trying to do 
> here, without resorting to Unsafe. Specifically, using a 
> MethodHandles.byteArrayViewVarHandle 
> <https://docs.oracle.com/javase/10/docs/api/java/lang/invoke/MethodHandles.html#byteArrayViewVarHandle(java.lang.Class,java.nio.ByteOrder)>()
>  
> that you would get once (statically), you should be able to peek into your 
> many different byte[] instances and extract a field of a different 
> primitive type (int, long, etc.) at some arbitrary index, without having to 
> wrap it up in the super-short-lived ByteBuffer in your example, and hope 
> for Escape analysis to take care of it...
>
> Here is a code example that does the same wrapping you were looking to do, 
> using VarHandles:
>
> import java.lang.invoke.MethodHandles;
> import java.lang.invoke.VarHandle;
> import java.nio.ByteOrder;
>
>
> public class VarHandleExample {
>
>     static final byte[] bytes = {0x02, 0x00, (byte) 0xbe, (byte) 0xba, (
> byte) 0xfe, (byte) 0xca};
>
>     private static class FileDesc {
>         static final VarHandle VH_intArrayView = MethodHandles.
> byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
>         static final VarHandle VH_shortArrayView = MethodHandles.
> byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN);
>         private final byte[] buf;
>         int bufPos;
>
>         FileDesc(byte[] buf, int headerPosition) {
>             bufPos = ((short) VH_shortArrayView.get(buf, headerPosition)) 
> + headerPosition;
>             this.buf = buf;
>         }
>
>         public int getVal() {
>             return (int) VH_intArrayView.get(buf, bufPos);
>         }
>     }
>
>
>     public static void main(String[] args) {
>         FileDesc fd = new FileDesc(bytes, 0);
>         System.out.format("The int we get from fd.get() is: 0x%x\n", fd.
> getVal());
>     }
> }
>
> Running this results in the probably correct output of:
>
> The int we get from fd.get() is: *0xcafebabe*
>
> Which means that the byte offset reading in the backing byte[], using 
> little endian, and even at not-4-byte-offset-aligned locations, seems to 
> work.
>
> NOTE: I have NOT examined what it looks like in generated code, beyond 
> verifying that everything seems to get inlined, but as stated, the code 
> would not incur an allocation or need an intermediate object per buffer 
> instance.
>
> Now, since this only works in Java9+, you could code it that way for those 
> versions, and revert to the Unsafe equivalent for Java 8-. You could even 
> convert the code above to code that dynamically uses VarHandle (when 
> available) without requiring javac to know anything about them (using 
> reflection and MethodHandles), and uses Usafe only if VarHandle is not 
> supported. Ugly ProtableVarHandleExample that does that (and would run on 
> Java 7...10) *might* follow...
>
> On Tuesday, August 7, 2018 at 1:55:35 PM UTC-7, Todd Lipcon wrote:
>>
>> Hey folks,
>>
>> I'm working on reducing heap usage of a big server application that 
>> currently holds on to tens of millions of generated FlatBuffer instances in 
>> the old generation. Each such instance looks more or less like this:
>>
>>     private static class FileDesc {
>>       private final ByteBuffer bb;
>>       int bbPos;
>>
>>       FileDesc(ByteBuffer bb) {
>>         bbPos = bb.getShort(bb.position()) + bb.position();
>>         this.bb = bb;
>>       }
>>
>>       public int getVal() {
>>         return bb.getInt(bbPos);
>>       }
>>     }
>>
>> (I've simplified the code, but the important bit is the ByteBuffer member 
>> and the fact that it provides nice accessors which read data from various 
>> parts of the buffer)
>>
>> Unfortunately, the heap usage of these buffers adds up quite a bit -- 
>> each ByteBuffer takes 56 bytes of heap, and each 'FileDesc' takes 32 bytes 
>> after padding. The underlying buffers themselves are typically on the order 
>> of 100 bytes, so it seems like almost 50% of the heap is being used by 
>> wrapper objects instead of the underlying data itself. Additionally, 2/3 of 
>> the object count are overhead, which I imagine contributes to GC 
>> scanning/marking time.
>>
>> In practice, all of the ByteBuffers used by this app are simply 
>> ByteBuffer.wrap(byteArray). I was figuring that an easy improvement here 
>> would be to simply store the byte[] and whenever we need to access the 
>> contents of the FlatBuffer, use it as a flyweight:
>>
>>   new FileDesc(ByteBuffer.wrap(byteArray)).getVal();
>>
>> ... and let the magic of Escape Analysis eliminate those allocations. 
>> Unfortunately, I've learned from this group that magic should be tested, so 
>> I wrote a JMH benchmark: 
>> https://gist.github.com/4b6ddf0febcc3620ccdf68e5f11c6c83 and found that 
>> the ByteBuffer.wrap allocation is not eliminated.
>>
>> Has anyone faced this issue before? It seems like my only real option 
>> here is to modify the flatbuffer code generator to generate byte[] members 
>> instead of ByteBuffer members, so that the flyweight allocation would be 
>> eliminated, but maybe I missed something more clever.
>>
>> -Todd
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to mechanical-sympathy+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to