float[] view of direct FloatBuffer + mapped objects (misery)

Wouldn’t this be great?

`
int size = 1024;

FloatBuffer buf = ByteBuffer.allocateDirect( size * Float.SIZE / 8 ).order(ByteOrder.nativeOrder()).asFloatBuffer();
float[] arr = buf.asFloatArray();

ShortBuffer buf = ByteBuffer.allocateDirect( size * Short.SIZE / 8 ).order(ByteOrder.nativeOrder()).asShortBuffer();
short[] arr = buf.asShortArray();
`

Then we could finally get around the performance-difference and handling between the two.

Is it possible to do this with JNI?

I worked a few hours on it, and it works now!

The float-array is directly mapped to a direct FloatBuffer, so the float[] is basicly in non-heap memory!

No JNI, no bytecode-weaving, and few dirty tricks:


      FloatPair pair = ArrayBufferTool.createFloatPair(1024);
      FloatBuffer buf = pair.buffer();
      float[] arr = pair.array();

      System.out.println("buf.capacity = " + buf.capacity());
      System.out.println("arr.length = " + arr.length);
      System.out.println();

      System.out.println("buf @ 0 = " + buf.get(0));
      System.out.println("arr @ 0 = " + arr[0]);
      System.out.println();
      buf.put(0, 3.1415f);
      System.out.println("buf @ 0 = " + bug.get(0));
      System.out.println("arr @ 0 = " + arr[0]);
      System.out.println();
      arr[0] = 1.234f;
      System.out.println("buf @ 0 = " + buf.get(0));
      System.out.println("arr @ 0 = " + arr[0]);
      System.out.println();

Output:


buf.capacity = 1024
arr.length = 1024

buf @ 0 = 0.0
arr @ 0 = 0.0

buf @ 0 = 3.1415
arr @ 0 = 3.1415

buf @ 0 = 1.234
arr @ 0 = 1.234

Note that you can’t (ever!) create a float[] for an existing FloatBuffer.

Last but not least: the FloatBuffer will never be GCed, as it is unknown whether float[] is still refering to it.

I hope somebody cares about my performance-topics this time :slight_smile:

As you may have realized, with my API I can map any object to a direct java.nio.Buffer (a float[] is just a regular object)

My structs are almost like C now: except that the object-overhead is embedded in the Buffer


class Vec
{
   int x, y, z;
}


// setup the direct 'heap'

int sizeof = MemoryBlock.sizeof(Vec.class); // 3 ints -> 12 bytes + 8 bytes overhead = 20 bytes
int instances = 1024;

ByteBuffer buffer = ...(sizeof * instances);
MemoryBlock block = new MemoryBlock(buffer);


// use a dummy instance to see which type the method should return

Vec dummy = new Vec();
Vec mapped1 = block.map(dummy, 0 * sizeof);
Vec mapped2 = block.map(dummy, 1 * sizeof);



// no performance penalty

mapped1.x = mapped2.y - mapped2.z;
mapped1.y = mapped2.x + mapped2.z;
mapped1.z = mapped2.y - mapped2.x;


// shift pointer, no objects alloced

mapped1 = block.map(mapped1, 43 * sizeof);
mapped2 = block.map(mapped2, 42 * sizeof);

The VM doesn’t know better than that those references point to real Objects.

Same here about the GC… the MemoryBlock will never (!) be GCed, as there could be dozens of Vec being mapped to it.

FYI: the object-overhead cannot be removed from the struct, as the VM needs it to treat a pointer like an Object.

Suggestions? :slight_smile:

Heh, congratulations. What you did sounds pretty cool and I’m sure I’d be very excited about it if I coded it or was using it, but I’m afraid I’m not deep enough into array buffer use to fully appreciate what you have here. I don’t believe I’ve seen anything of this type before though.

Good job none-the-less. :slight_smile:

Are you using sun.misc.Unsafe to do these tricks? If so, they will probably work great up until the first garbage collection, at which point the JVM will core dump. You can’t do these kinds of operations (casting random pieces of memory to Objects) safely. Work is continuing in HotSpot to make NIO Buffers go faster in all situations so that you can stop using arrays and just use Buffers everywhere in your application.

Infact, I got that crash earlier, but fixed it.

It’s not crashing, ever, not with a normal GC, and not with a full GC.

Further, I know that work is being done in this area. I’d hate to be a bit blunt, but after several years, the performance is still not comparable (see my other posts in this sub-forum). And having mapped Objects adds functionality, which is unlikely Sun is going to implement.

If Sun would add mapped objects (and bump buffer-performance), I’d be more than happy to ditch my code, really.

Imagine an API that crunches float[]s, and you only have FloatBuffers laying around, or vice versa. One would have to copy between the datastructures, while with shared resources, you can process the float[] the API expects (or that simply performs faster), work with the FloatBuffer in say LWJGL. (and when doing engine-logic, map the data into Objects for convenience)

If you’d be willing to post your code, I’d be happy to look at it and point out any potential problems, but in the absence of that I have to warn people on these forums not to get too excited. I don’t think there’s any possible way this can be done safely given the current primitives in the JVM and Java libraries.

That’s the reason I didn’t post it right away, ‘first feeling the waters’. (is that an expression?)

I’ll PM you in a few hours. Got to go to school now :slight_smile:

Java class at 10:40 ;D (about how Swing works, without being taught how object references work!)

Would it be possible to overload ‘MemoryBlock.map’, so that I could do:


Vec mapped = block.map(Vec.class, 1*sizeof);

or is the dummy allocation needed?

Signature would be like this:


<T> T MemoryBlock map(Class<T> type, int size) // throws UnsupportedTypeException or whatever ?
{ .. }

Of course creatiing an instance of ‘T’ with reflection is slow, but this is an one time init thing, right? IMHO it’s more elegant - but of course the original method should remain too…

To be honest, the passed argument isn’t only for determining type, there are more things going on under the hood.

I need that instance. :wink:

To be honest, I’m surprised you got this working. Java objects aren’t necessarily located at a fixed place in memory. What happens if the GC decides to move the mapped Object to a different place in memory?

I’d really like to see the code behind this, it sounds like a neat hack.

I’m managing my own references (AKA smart pointers). The GC only ‘touches’ my mapped objects (modifies the object-header every once in a while), but never changes the pointer of my references.

This code:
long pntr = MemoryBlock.getPointer(obj);
always returns the same pointer (in direct memory) after a lot of GCs and full GCs.

That’d depend on the GC algorithm being used. I believe the compacting GC will bugger it right up.

Perhaps you can pin the objects in JNI using getCritical thingies?

Cas :slight_smile:

I’ll second that.

The JNI spec says you’re only allowed to use get*Critical for small bits of code. I don’t think you can pin objects in memory for an extended period of time. The spec also mentions that these methods don’t necessarily pin the object. You still might get a copy.

Would it make sense to use Buffers to backup vectors instead of a class Vec { float x; float y; float z} thing?

Something like a class VectorBuffer implemented on a Float Buffer.

We could even use generics for this: class VectorBuffer where T can be ByteBuffer, FloatBuffer, etc

I thought about it, like a sliding window, or struct.

It would make it a heck of a lot more safe (1 type per MemoryBlock)

Then you don’t have to pass the offset in bytes (n * sizeof), but in structs (n)

I can’t however make a {Type}Buffer at runtime, because you need it at compile-time to be usable.

StructBuffer struct = new StructBuffer(ByteBuffer bb, Vec.class);
Vec mapped1 = struct.get(n+13);
Vec mapped2 = struct.get(n+14);

I’m testing things now, it has serious advantages over the MemoryBlock class

Sent PM to Ken Russell…

Without a doubt he’ll advice (more or less) against it, as it can’t be 100.00% relied on :slight_smile:

I think it’s stable enough for games, I can’t manage to crash it.

Current usage:


      int instances = 2;
      StructBuffer<Vec> structBuffer = new StructBuffer(Vec.class, instances);

      Vec vec1 = structBuffer.get(0);
      Vec vec2a = structBuffer.get(1);
      Vec vec2b = structBuffer.get(1);

      // fill vec
      vec1.x = 4;
      vec1.y = 5;
      vec1.z = 6;
      System.out.println(vec1);

      // write 13 to buffer at the position of vec.y
      int sizeof = structBuffer.sizeof();
      int yOff = structBuffer.searchFieldOffset("y");
      structBuffer.backing().putInt((0 * sizeof) + yOff, 13);
      System.out.println(vec1);

      System.out.println("vec@2a == vec@2b: " + (vec2a == vec2b));

Output:


Vec[x=4,y=5,z=6]
Vec[x=4,y=13,z=6]
vec@2a == vec@2b: true

As Riven mentioned he sent me his source code (which, admittedly, is very clever). The basic trick is using sun.misc.Unsafe to fake up object references which point outside the garbage-collected heap. While this is a very clever trick, it doesn’t work in the general case. The HotSpot garbage collector is a precise copying collector and at basically any time it may encounter one of these faked-up object instances and copy it into the heap, destroying the association between (in this case) the Vec and the backing Buffer. Actually, thinking more about how the mark-sweep collector works, I’m really not sure what will happen when it traverses one of these faked-up object instances. It may “just” leave the object header in a corrupted state, or possibly worse. Again, a GC and these nasty side-effects might happen at any time (basically during the execution of any Java bytecode), so this trick is completely unsuitable for use in any situation except as an interesting thought experiment.

You may want to look at the code GlueGen autogenerates on the Windows platform for JOGL. It creates Java classes which map to C structs. While the memory isn’t overlaid (it uses New I/O internally), the mapping between fields of C structs and methods in the Java classes is very straightforward and easy to deal with. Techniques such as this are legal and are what I’d recommend as an alternative.

While respecting (and accepting) your advice, in the supplied code there was a stress-test which showed the (default?) GC doesn’t copy the mapped objects’s pointers / references to heap-memory. Could you explain this behaviour?

Could you please tell me whether Sun is working (or: going to work) on mapped-objects?

The only thing that would be required to make this work, is to make the GC never touch any object outside the heap, which seems to be quite easy to implement to me.

Thanks for your time.