Java-Gaming.org Hi !
Featured games (83)
games approved by the League of Dukes
Games in Showcase (513)
Games in Android Showcase (120)
games submitted by our members
Games in WIP (577)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1] 2
  ignore  |  Print  
  A poor man's struct (err, MappedObject)  (Read 10972 times)
0 Members and 1 Guest are viewing this topic.
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Posted 2006-03-17 14:52:39 »

I guess I have a reputation of microbenchmarks and struct-rambling over here...and here we go again Wink



I tried to make an effort of making a struct-framework work for the time being, until Sun ships them in the JIT/VM in Java 7.0 (or later?).

It is basicly a source-code generator and a utility class to handle the 'sliding window' behaviour of the structs.

For all those who don't have the patience to read the whole thing and want to see some performance-benchmarks..
1  
2  
3  
4  
5  
6  
7  
Three datasets of 5000 3d-vectors:
     c = at * ( b - a )

buffer = 15us   // FloatBuffer(3 * 5000), absolute put/get
struct = 24us   // my struct implementation
arrays = 40us   // float[3 * 5000]
object = 74us   // Vec3[1 * 5000]



*gasp*


First there is the StructGenerator class:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
public static String createSourcecode(String className, Class[] types, String[] names)

Usage:
class Particle
{
   float x, y, z;
   int state;
}

Class[] primitives = new Class[]{float.class, float.class, float.class, int.class};
String[] names = new String[]{"x", "y", "z", "state"};
String code = StructGenerator.createSourcecode("Particle", primitives, names);


Now we have sourcecode we can compile. This is a one-time effort.



The generated class has a static method that gets us a StructBuffer for that specific class:
1  
2  
int particleCount = 1024;
StructBuffer buf = Particle.createStructBuffer(particleCount );



The StructBuffer holds the position of the 'sliding window'. We will use the Particle like this:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
Particle p = new Particle(buf);

buf.position(13);
p.x(0.4f);
p.y(0.5f);
p.z(0.6f);
p.state(-5);

buf.position(14);
p.x(0.3f);
p.y(0.2f);
p.z(0.1f);
p.state(42);



So the Particle isn't sliding over the data, but the data is sliding underneath the Particle.

What if we want two Particles accessing the same dataset?

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
StructBuffer pBuf0 = buf;
StructBuffer pBuf1 = buf.duplicate();

Particle p0 = new Particle(pBuf0);
Particle p1 = new Particle(pBuf1);

pBuf0.position(11);
p0...;

pBuf1.position(9);
p1...;

//  p0.x = p1.y
p1.x( p0.y() );



Once we're done manipulating the particle-data, we can extract the ByteBuffer from the StructBuffer like this:

1  
ByteBuffer bb = buf.getBacking();



As said above, the performance is 3 times faster (!!) than iterating over an array of  "struct-objects" (Vec3) and about 66% the speed of directly manipulating the FloatBuffers. This will only get better once Sun natively implements structs in the VM. It takes however the burden of massive gc() and object-creation.


I'm finalizing the sourcecode at the moment, but performance is kinda stuck at this level  (which is nothing to be ashamed about IMO Smiley)

Is this a usable design / framework, are there suggestions how to change things? I'd like to hear your comments.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline zero

Junior Duke





« Reply #1 - Posted 2006-03-17 17:21:07 »

hey there,

at first I want to apologize for commenting without being well informed about the whole structs discussion - though I read some postings and the RFE..

well, one thing what I really like to be cleared is the difference between Structs and MappedObjects  Roll Eyes
As I said, I don't have the definitions, which may be included in some other posts, in my head, but in order to minimize misunderstandings, followiing describes the way I will use both term here in this post:

Structs:
An automatic mechanism to copy a class from or to a buffer (and only a buffer). Classes are still reference types and can be null, in contrast to C# structs, which are value types. (-> no default constructor is needed for java structs)

MappedObjects:
An interpretation of a continous segment of a buffer, with shared memory. This means any modification to the Objects' (marked) attributes or properties will reflect in a change of the buffer. Futher two or more Objects can be mapped to overlapping regions of the buffer.



OK, with this I can start commenting on your implementation:

Firstly, from my point of view you are talking about a mapping between a Buffer and Objects. The main difference to mapped objects, like described above, is that you usaully only have a few instances - as much as different structs have to be accessed at once. Since a non VM integrated type of an mapped object always has at least a single reference to a ByteBuffer, your technique should save a decent amount of memory. The other side of the coin is that IMHO the best argument for mapped objects is, to avoid those fency get and put method calls on a buffer. Your implementation however, only puts it to a slightly higher level: from primitves to objects (which are only allowed have primitive attributes?). I don't like such a coding style, I find array of classes much more natural.

Btw:
As from my understading your current implementation needs two compilations, right?
If so, the following me be interesting to you:
With Java 6, you can dynamically compile a dynamically written class, load and instantiate it in the same runtime. Smiley (take a look a javax.tools)



Well, back to topic: I personally prefer the struct-way, because to my experience multiple mappings on  the same data usually leads to undesirable side effects. Especially in multi-threaded applications this can be really horrible keeping safe.
What I really like about Java is that the language tries to avoid this. IMHO mapped objects are best compered to pointers, which were wisely not integrated into Java (in contast to C#). What'll be the next thing, an extension which allows you to take own control of finalization? (like delete in C++)

The only argument against structs may be that a copy operation from or to a bufferis needed, but since structs are usually small Objects that shouldn't have a strong effect on the performace. Therefore I currently would do an implentation like this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
public interface Struct {
   void load(ByteBuffer src);
   void save(ByteBuffer dst);  
}

public class Particle3 implements Struct {
   public float x;
   public float y;
   public float z;
   public int state;

   public void save(ByteBuffer src) {
      final FloatBuffer fb = src.asFloatBuffer;
      x = fb.get();
      y = fb.get();
      z = fb.get();
      state = fb.asIntBuffer().get();
   }

   public void save(ByteBuffer dst) {
      dst.asFloatBuffer.put(x).put(y).put(z).asIntBuffer().put(state);
   }
}


in future, I hope to change this to:
(we should be possible to do by ourself with Java 6 and annotations)

1  
2  
3  
4  
5  
6  
7  
8  
@Struct //--> this will automatically add the interface and generate an implementation/overide the old one
public class Particle3 {
   public float x;
   public float y;
   public float z;
   // how about: @Endian=little ? this would define how the data is saved/loaded from the ByteBuffer
   public int state;
}



finally, dealing with a buffer can be simplified to this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
public absract class StructBuffer extends Buffer {

   private ByteBuffer data;

   public StructBuffer get(Struct struct) {
      struct.load(data);
      return this;
   }
   public StructBuffer put(Struct struct) {
      struct.save(data);
      return this;
   }

   //..
}



Anyway, I'm very curious how the implementation in dolphin will look like. IMHO Java Team did a great job in the past, so I won't expect less for the future  Smiley
Offline zero

Junior Duke





« Reply #2 - Posted 2006-03-17 17:31:31 »

ah just one thing that seams a bit problematic with your implemantation:

maybe it is difficult to add methods the the generated Particle class, right?
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #3 - Posted 2006-03-17 18:19:42 »

First of all, yes, it's not a very clean and neat design, but we don't have anything else as of yet.

Second, your FloatBuffer I/O is increadibly slow (relative put/get) and asIntBuffer() creates a new object.

Third, structs are not meant as copies, your StructBuffer impl. does a lot of copying, which is disasterous for performance.



Last (@zero) it's not difficult at all to add new methods, it's just plain generated source-code, you can do whatever you want with it.

The source-code looks like this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
public class Particle
{
   private static final int size = 16;



   public static final StructBuffer createStructBuffer(int elements)
   {
      ByteBuffer bb = ByteBuffer.allocateDirect(elements * size);
      bb.order(ByteOrder.nativeOrder());
      return new StructBuffer(Particle.class, bb, size);
   }

   private final StructBuffer buf;



   public Particle(StructBuffer buf)
   {
      // lots of stuff here
   }



   public final StructBuffer getStructBuffer()
   {
      return buf;
   }



   public final void x(float x)   {      access.putFloat((buf.offset + 0) + base, x);   }
   public final void y(float y)   {      access.putFloat((buf.offset + 4) + base, y);   }
   public final void z(float z)   {      access.putFloat((buf.offset + 8) + base, z);   }
   public final void state(int state)   {      access.putInt((buf.offset + 12) + base, state);   }

   public final float x()   {      return access.getFloat((buf.offset + 0) + base);   }
   public final float y()   {      return access.getFloat((buf.offset + 4) + base);   }
   public final float z()   {      return access.getFloat((buf.offset + 8) + base);   }
   public final int state()   {      return access.getInt((buf.offset + 12) + base);   }
}

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline princec

JGO Kernel


Medals: 407
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #4 - Posted 2006-03-17 18:39:17 »

One of the main ideas of structs/mapped objects/whatevers was to avoid memory copies. You are completely able to product the affect of structs by writing bytecode-rewriters and such but at the end of the day any savings you make in typing syntactic sugar are buggered by the VM being slow at doing the memory copy thing. Just 2p comment on the issue, not really to do with Riven's implementation.

Cas Smiley

Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #5 - Posted 2006-03-17 18:43:11 »

Well, i'm glad i don't do any memory copies Wink

Besides that, yes, byte-code transformers (fields->methods in both struct-class and classes using it) are much much better, feel to write one? No? Me neither Grin

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline zero

Junior Duke





« Reply #6 - Posted 2006-03-18 07:31:23 »

Second, your FloatBuffer I/O is increadibly slow (relative put/get) and asIntBuffer() creates a new object.

Ahm, relative put/get are slower? Sorry, didn't know that. Thought less arguments make it faster, but change the buffer's position may have a greater impact..

Anyway, replace my code with and copying is as fast as your access to the struct components:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
 public void load(ByteBuffer src) {
    final int pos = src.position();
    x = src.getFloat(pos);
    y = src.getFloat(pos+4);
    z = src.getFloat(pos+8);
    state = src.getInt(pos+12);
    src.position(pos+16);
}

public void save(ByteBuffer dst) {
    final int pos = src.position();
    dst.putFloat(pos, x);
    dst.putFloat(pos+4, y);Float
    dst.putFloat(pos+8, z);
    dst.putInt(pos+12, state);
}



Further I noticed a generic version of the StructBuffer may be needed for the marks and positions:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
public absract class StructBuffer<S extends Struct> extends Buffer {

public <T extends Struct> StructBuffer<T> asStructBuffer(Class<T> structType) {
...
}

public <T extends Struct> StructBuffer<T> allocate(int capacity, Class<T> structType) {
...
}

}




Third, structs are not meant as copies, your StructBuffer impl. does a lot of copying, which is disasterous for performance.

You are kidding, don't you?

One usaually does ONE copy per update/frame, e.g. for dynamic geometry sent to the graphics-card. Even with a heavy dynamic geometry load this will NEVER be a performance penalty or even the bottleneck of an application.
Its just a simple,fast copy, and if the data is dynamic it will be modified before the copy. Take care about the performance of these operations.
Java's that great for that, making use them can drastically increase performance. I've opened another thread (Why mapped objects (a.k.a structs) aren't the ultimate (performance) solution) for explaining a possible use, because I don't want to hijack yours.  Smiley

Btw. in C# + DirectX geometry is always copied when sent to a VertexBuffer since structs are always copied by only assigning them to a variabke (e.g. putting hem into an array) or passing them as an arguement (except using the ref keyword). nobody at microsoft complains about performonce. Further it is very convenient, since copy to a stream/buffer is as I described automatically enabled for structs like I advice.
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #7 - Posted 2006-03-18 08:50:56 »

My StructBuffer implementation is already generic.

Further, your load()/save() methods almost make me cry, ByteBuffer.getFloat() is even much slower than FloatBuffer.get() which is much slower than FloatBuffer.get(i) (which is much slower than Unsafe.getFloat(pointer) - which I use )

And if your benchmark tells you they have equal speed, your benchmark is flawed.


And about your copying, you're copying a_lot_of_times_per_frame/update, basicly for every struct-access, not a 'batch-write' to the gpu.


It sounds to me you never worked with NIO and performance-critical-code, seeing the trival mistakes you make. (no offence intended)

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline zero

Junior Duke





« Reply #8 - Posted 2006-03-18 09:14:34 »

My StructBuffer implementation is already generic.

Further, your load()/save() methods almost make me cry, ByteBuffer.getFloat() is even much slower than FloatBuffer.get() which is much slower than FloatBuffer.get(i) (which is much slower than Unsafe.getFloat(pointer) - which I use )

And if your benchmark tells you they have equal speed, your benchmark is flawed.

I have no benchmark, but what I can tell is that if I simply comment out the lines which fill the buffers send to the graphics-card every frame, the framerate doesn't increase at all. So copying the data to the buffer has for me no influence on the performance (and I copy about half a million vertices (positions and normals only every frame).

And about your copying, you're copying a_lot_of_times_per_frame/update, basicly for every struct-access, not a 'batch-write' to the gpu.

It sounds to me you never worked with NIO and performance-critical-code, seeing the trival mistakes you make. (no offence intended)

You didn't uderstand my version. in my code the 'sructs' are normal java classes. Accessing a field means accessing a field, no buffer at all. Once all modificartions is done, I either:

- copy the data to a buffer, I have allocated once inside the VM and send it to graphics-card with glBufferSubData
- or directly use the the memory from openGL (glMapBuffer) and copy data into the returned ByteBuffer

--> one copy per frame
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #9 - Posted 2006-03-18 09:33:41 »

Sounds like your geometry-related-code is not your bottleneck at all. So why bother?

object->fields->buffer->gpu is very inefficient, yet might be 'good enough' for you. Don't project that to the general case.



Quote
You didn't uderstand my version. in my code the 'sructs' are normal java classes.

That sounds pretty much like: you don't understand my Ships, they are Cars.



I understand everybody not dealing with raw performance on NIO-data disgusts Structs... that's good, they are not meant for these people.

Anybody any comment on the actual API, instead of the chit-chat about the necessity of structs? Smiley Thanks!

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline zero

Junior Duke





« Reply #10 - Posted 2006-03-18 09:41:51 »

Sounds like your geometry-related-code is not your bottleneck at all. So why bother?
Right, I don't have any problems with performance, so I don;t bother about structs Smiley

object->fields->buffer->gpu is very inefficient, yet might be 'good enough' for you. Don't project that to the general case.

You didn't uderstand my version. in my code the 'sructs' are normal java classes.

That sounds pretty much like: you don't understand my Ships, they are Cars.

No they aren't that inefficent. this is a copy operations, constant in time. Linear for an array. My whole point is that this will (almost) never be the bottleneck of a REAL application. And if so Java may the wrong language for you, sorry.
Invent a new one where all data is stored in some kind of buffer and Objects only mapped to them, this would be great for you, right? (also no offense intended Smiley)
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #11 - Posted 2006-03-18 09:48:22 »

Sounds like your geometry-related-code is not your bottleneck at all. So why bother?
Right, I don't have any problems with performance, so I don;t bother about structs Smiley

Then don't hijack this thread talking about things that you don't want, don't need, and don't understand.

I was interested in comments on the API, not about "do we need structs?" - yes, some of us need them, most of us dont.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline zero

Junior Duke





« Reply #12 - Posted 2006-03-18 10:01:45 »

Then don't hijack this thread talking about things that you don't want, don't need, and don't understand.

I was interested in comments on the API, not about "do we need structs?" - yes, some of us need them, most of us dont.

hey, don't blame me, that's why I opened the other thread. if you like, please comment there whether you think the kind of optimization I discussed in the 2nd half is possible with your struct implementation. that would be contructive  Smiley

Further, I comented on your API, that I don't like the style encapsulating a single struct into a buffer class, don't remember that?

Btw you told that ByteBuffer.putFloat is slower than FloatBuffer.put, but you used this (access.putFloat,..). did I miss s.th. ?
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #13 - Posted 2006-03-18 10:11:00 »

Ofcourse you don't like the API/design when you don't need it, because it's kinda "hackery" and non-Java, but required when performance is all one cares about.

And no, i'm not encapsulating a *single* struct into a buffer-class (check the two-structs-example). And the StructBuffer class is not bound to the Particle-class, but can be used for any generated struct.


About access.putFloat() - My own quote:
Quote
ByteBuffer.getFloat() is even much slower than FloatBuffer.get() which is much slower than FloatBuffer.get(i) (which is much slower than Unsafe.getFloat(pointer) - which I use)

So I'm using the Unsafe-class (in a 100% safe way)

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline zero

Junior Duke





« Reply #14 - Posted 2006-03-18 10:18:13 »

So I'm using the Unsafe-class (in a 100% safe way)

sun.misc.Unsafe, which only ships with SUN' Java, right?
Offline mabraham

Junior Duke





« Reply #15 - Posted 2006-03-19 18:55:52 »

So I'm using the Unsafe-class (in a 100% safe way)

sun.misc.Unsafe, which only ships with SUN' Java, right?

Look what point exactly are you trying to make here?  Sure using Unsafe is hackery, but didn't Riven admit to that right from the start?  I've been following this discussion, found it refreshing for a while but am getting a little concerned now, cos I can't help feeling you missed the entire point!  You consider structs as value types that maintain their own state.  Riven's structs merely wrap existing data.  You provide convenience methods to read from and write to a buffer.  Riven's structs provide a (structured, fast-path) view on bulk data.  Why do you bluntly suggest using a different language, when someone has just shown a way of getting really good performance out of Java, on a silver plate.  This may not be your cup of tea, and to be fair, I wouldn't use this because of the Unsafe bit.  But still, I'm fascinated! Smiley

I hope I haven't missed anything but anyway, that's my take of this thread so far.

Cheers,
Matt.
Offline princec

JGO Kernel


Medals: 407
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #16 - Posted 2006-03-19 19:10:08 »

Fecking structs, I wish I'd never called them that. The whole point is nothing to do with C or structs in C or "value types". It is all about providing object-oriented access to data held in ByteBuffers in an efficient manner which is easy to implement in the current VMs, without breaking any semantics in the language or the specs.

Cas Smiley

Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #17 - Posted 2006-03-20 08:19:12 »

This may not be your cup of tea, and to be fair, I wouldn't use this because of the Unsafe bit.  But still, I'm fascinated! Smiley

I can take out the unsafe-bit quite easily, reducing performance by factor 2 (when using fancy {...}Buffers) or factor 4-8 when using ByteBuffers directly Wink


Fecking structs, I wish I'd never called them that.
Well I know they are not structs, but it's the closest thing to it, in Java, right? When I'd call them "sliding window objects" nobody would have a clue.

Anyway, when I code the bytecode transformer on a rainy afternoon, you'll be the first to know.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline tusaki

Junior Duke


Medals: 1


In a mad world only the mad are sane.


« Reply #18 - Posted 2006-03-20 08:38:23 »

Anyway, when I code the bytecode transformer on a rainy afternoon, you'll be the first to know.

It's raining where I am right now! and its almost afternoon Grin
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #19 - Posted 2006-03-20 18:24:57 »

I'm halfway with a new design and bytecode-transformer (and they say the last 20% takes 80% of the time, arg!)


This is the input:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
@MappedObject(sizeof = 12)
public class Vector
{
   @MappedField(offset = 0)
   public float x;

   @MappedField(offset = 4)
   public float y;

   @MappedField(offset = 8)
   public float z;



   public final float length()
   {
      return (float) Math.sqrt(x * x + y * y + z * z);
   }
}




This is the output: (note the class extends Vector, so all methods will still be available)
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
public final class MappedVector extends Vector
{
   public static final int sizeof = 12;



   public MappedVector()
   {
      this.buf = null;
   }



   public MappedVector(MappedObjectBuffer<MappedVector> buf)
   {
      if (buf.type != MappedVector.class)
         throw new IllegalArgumentException("MappedByteBuffer type must be: MappedVector");

      this.buf = buf;
   }

   private final MappedObjectBuffer<MappedVector> buf;



   // MappedObject structure:
   // -> mapped field "x" at offset #0
   // -> mapped field "y" at offset #4
   // -> mapped field "z" at offset #8

   // setters
   public final void x(float x)   {      buf.putFloat(0L, x);   }
   public final void y(float y)   {      buf.putFloat(4L, y);   }
   public final void z(float z)   {      buf.putFloat(8L, z);   }

   // getters
   public final float x()   {      return buf.getFloat(0L);   }
   public final float y()   {      return buf.getFloat(4L);   }
   public final float z()   {      return buf.getFloat(8L);   }
}




Usage: (without bytecode transformations)
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
MappedObjectBuffer<MappedVector> aBuf = MappedObjectBufferFactory.create(new MappedVector(), elements);



      MappedVector aVec = aBuf.getMappedObject();
      MappedVector bVec = bBuf.getMappedObject();
      MappedVector cVec = cBuf.getMappedObject();

      aBuf.rewind();
      bBuf.rewind();
      cBuf.rewind();

      while (a.hasRemaining())
      {
         cVec.x(aVec.x() + t * (bVec.x() - aVec.x()));
         cVec.y(aVec.y() + t * (bVec.y() - aVec.y()));
         cVec.z(aVec.z() + t * (bVec.z() - aVec.z()));

         aBuf.next(); // equals: aBuf.position(aBuf.position() + 1);
         bBuf.next();
         cBuf.next();
      }




Usage: (with bytecode transformations)
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
MappedObjectBuffer<MappedVector> aBuf = MappedObjectBufferFactory.create(new MappedVector(), elements);



      MappedVector aVec = a.getMappedObject();
      MappedVector bVec = b.getMappedObject();
      MappedVector cVec = c.getMappedObject();

      aBuf.rewind();
      bBuf.rewind();
      cBuf.rewind();

      while (a.hasRemaining())
      {
         cVec.x = aVec.x + t * (bVec.x - aVec.x); // the compiler won't complain, because (it thinks) we extend Vector and access its fields
         cVec.y = aVec.y + t * (bVec.y - aVec.y);
         cVec.z = aVec.z + t * (bVec.z - aVec.z);

         aBuf.next();
         bBuf.next();
         cBuf.next();
      }




As MappedVector is a subclass of Vector, you can simply do this:

1  
Vector vec = a.getMappedObject();



With the bytecode stuff done you can access the fields, which will be converted into method-calls at runtime.

And with the bytecode classes done, it will be trival to remove the source-code generation / compilation process:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
MappedObjectBuffer mob = MappedObjectBuffer.create(new Vector(), 10000);
Vector vec = mob.getMappedObject();

mob.position(13);
vec.x = 4;
vec.y = 5;
vec.z = 6;
vec.length();

mob.position(14);
...

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline Raghar

Junior Duke




Ue ni taete 'ru hitomi ni kono mi wa dou utsuru


« Reply #20 - Posted 2006-03-20 20:00:05 »

Fecking structs, I wish I'd never called them that. The whole point is nothing to do with C or structs in C or "value types". It is all about providing object-oriented access to data held in ByteBuffers in an efficient manner which is easy to implement in the current VMs, without breaking any semantics in the language or the specs.


I use term dipper for simillar structures. That things you talked about might be called memory mapped, or siliding window dipper.
Offline princec

JGO Kernel


Medals: 407
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #21 - Posted 2006-03-20 21:13:05 »

How are you going to implement write-through and read-through for field accesses directly to the underlying data though?

Cas Smiley

Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #22 - Posted 2006-03-20 21:28:38 »

By replacing all bytecode-level calls to the fields with bytecode-level calls to the methods.

So every class that is loaded, has to be checked for those fields, which is possible with java.lang.instrument.ClassFileTransformer.

So adding the transformer has to be one of the first things the app does after launch.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline krausest

Junior Duke


Exp: 15 years


I love YaBB 1G - SP1!


« Reply #23 - Posted 2006-03-21 20:25:20 »

Riven, I hate to stop you getting that bytecode-transformer finished ;-)
but I still have little insight in which buffer methods are fast and which aren't. There once was a thread on that (http://www.java-gaming.org/forums/index.php?topic=11385.0 ) but it ended that at least for mustang, all buffer methods should perform excellent.

So I learned that absolute puts and gets are faster than relative puts and gets, aren't they?
Does it make a difference between e.g. ByteBuffer.putFloat().. and converting the ByteBuffer to a FloatBuffer and then calling put?
What else are the take away points to make direct buffer access fast?

Thanks a lot!
Stefan
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #24 - Posted 2006-03-21 21:17:48 »

Performance factors (my own experience, YMMV)

Virtual machine   Sun 1.5.0_06 "server"
PlatformWinXP, P4 2.4GHz / 533MHz FSB, 512MB PC2700


Unsafe.putFloat(long, float)125%
FloatBuffer.put(int, float) (standard)100%
FloatBuffer.put(float)50%
ByteBuffer.putFloat(int, float)15%
ByteBuffer.putFloat(float)14%

Only take this as a reference, and run your own benchmarks please

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline HamsterofDeath

Junior Duke




Java games rock!


« Reply #25 - Posted 2006-03-21 21:28:06 »

how to put/get floats/ints/etc using the unsafe-class?

i've looked at sun's directbytebuffer. it seems i can allocate, access and free memory using Unsafe pretty simply. the only reason not using a bytebuffer when going for performance seems to be that a range check is performed at every access. however, i found a lot of  "anInt << 0"-bitshifts. my brain, and my ide also say that these are completely pointless. what are they good for?

[some time later]

i wrote a small benchmark, but it lies. the java float array is, depening on some fine tuning, up to 30 times faster than accessing the data via Unsafe. i guess the vm is understanding my benchmark and starts to cheat.

Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #26 - Posted 2006-03-22 12:09:01 »

The way one can access the pointers is considered so "not done", that I hestitate a little to just post it in this thread for everybody to use and abuse.

Use the forum-search and who-knows what you will find Wink

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline HamsterofDeath

Junior Duke




Java games rock!


« Reply #27 - Posted 2006-03-22 12:45:08 »

here's my result:
Quote
Array time: 4.799727 ms
unsafe time 29.210326 ms
Floatbuffer time: 23.882264 ms
Directbytebuffer2float time: 46.760005 ms

it seems that arrays are a LOT faster than any buffer available.
running on 1.5.0_06 -server
wtf?
looking at this, i have to ask: what are buffers good for? why is the unsafe access that slow? shouldn't it be faster?

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.nio.FloatBuffer;
import java.nio.ByteBuffer;


public class UnsafeDemo
{
   private static final int SIZE = 10000000;
   private static final int LOOPS = 50;


   public static void main(String[] args) throws Exception
   {
      Unsafe unsafe = getUnsafe();
      System.out.println("Unsafe = " + unsafe);
      System.out.println("  addressSize() = " + unsafe.addressSize());
      System.out.println("  pageSize() = " + unsafe.pageSize());
      System.out.println("  pageSize() = " + unsafe.pageSize());
      int cap = SIZE;//10m
      int ps = unsafe.pageSize();
      long base = unsafe.allocateMemory(cap + ps);
      long pointer;
      unsafe.setMemory(base, cap + ps, (byte) 0);
      if (base % ps != 0)
      {
         // Round up to page boundary
         pointer = base + ps - (base & (ps - 1));
      }
      else
      {
         pointer = base;
      }
      for (int i = 0; i < 5; i++)
      {
         benchArray();
         benchUnsafe(unsafe, pointer);
         benchBuffer();
         benchDirectBuffer();
         System.out.println("-------");
      }
      unsafe.freeMemory(pointer);
   }

   private static void benchDirectBuffer()
   {
      ByteBuffer fb = ByteBuffer.allocateDirect(SIZE);
      //unsafe benchmark
      int INTS = SIZE / 4;
      float[] dummy = new float[INTS];
      for (int i = 0; i < INTS; i++)
      {
         fb.putFloat(i,(float) (Math.random() * 1000));
      }
      long time = System.nanoTime();
      float x = 0;
      for (int i = 0; i < INTS; i++)
      {
         x += fb.getFloat(i);
      }
      System.out.println("Directbytebuffer2float time: " + (System.nanoTime() - time) / 1000000.0d + " ms");
      System.out.println("x array= " + x);
   }

   private static void benchUnsafe(final Unsafe unsafe, long pointer)
   {
      //unsafe benchmark
      int INTS = SIZE / 4;
      long absAdr = pointer-4;
      for (int i = 0; i < INTS; i++)
      {
         unsafe.putFloat(absAdr+=4, (float) (Math.random() * 1000));
      }
      long time = System.nanoTime();
      int x = 0;
      absAdr = pointer-4;
      for (int i = 0; i < INTS; i++)
      {
         x += unsafe.getFloat(absAdr+=4);
      }
      System.out.println("unsafe time " + (System.nanoTime() - time) / 1000000.0d + " ms");
      System.out.println("x unsafe= " + x);
   }

   private static void benchArray()
   {
      //unsafe benchmark
      int INTS = SIZE / 4;
      float[] dummy = new float[INTS];
      for (int i = 0; i < INTS; i++)
      {
         dummy[i] = (float) (Math.random() * 1000);
      }
      long time = System.nanoTime();
      float x = 0;
      for (int i = 0; i < INTS; i++)
      {
         x += dummy[i];
      }
      System.out.println("Array time: " + (System.nanoTime() - time) / 1000000.0d + " ms");
      System.out.println("x array= " + x);
   }

   private static void benchBuffer()
   {
      FloatBuffer fb = FloatBuffer.allocate(SIZE/4);
      //unsafe benchmark
      int INTS = SIZE / 4;
      float[] dummy = new float[INTS];
      for (int i = 0; i < INTS; i++)
      {
         fb.put(i,(float) (Math.random() * 1000));
      }
      long time = System.nanoTime();
      float x = 0;
      for (int i = 0; i < INTS; i++)
      {
         x += fb.get(i);
      }
      System.out.println("Floatbuffer time: " + (System.nanoTime() - time) / 1000000.0d + " ms");
      System.out.println("x array= " + x);
   }


   public static Unsafe getUnsafe() throws Exception
   {
      Unsafe unsafe = null;

      try
      {
         Class uc = Unsafe.class;
         Field[] fields = uc.getDeclaredFields();
         for (int i = 0; i < fields.length; i++)
         {
            if (fields[i].getName().equals("theUnsafe"))
            {
               fields[i].setAccessible(true);
               unsafe = (Unsafe) fields[i].get(uc);
               break;
            }
         }
      }
      catch (Exception ignore)
      {
      }

      return unsafe;
   }
}


for completition, the client result:
Quote
Array time: 24.679127 ms
unsafe time 40.004324 ms
Floatbuffer time: 23.507286 ms
Directbytebuffer2float time: 59.796623 ms

what does that tell us?
array access is about 10 times faster when using the server vm. rofl?

jrockit:
Quote
Array time: 5.091692999999999 ms
unsafe time 29.136017 ms
Floatbuffer time: 5.340409 ms
Directbytebuffer2float time: 11.336103 ms

*giving up*
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #28 - Posted 2006-03-22 12:57:04 »

You have a SIZE of 10M (10MB !!) which does an awful lot of cache-flushing.

On Unsafe code:
1  
2  
3  
4  
for (int i = 0; i < INTS; i++)
      {
         x += unsafe.getFloat(absAdr+=4);
      }


You're doing 2 increments per loop-iteration here. Bit unfair!



Change your benchmark to a real-world scenario and you'll get very different results.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 817
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #29 - Posted 2006-03-22 13:00:53 »

Arg!

FloatBuffer fb = FloatBuffer.allocate(SIZE/4);
uses a float[] as backing

Please do:
ByteBuffer bb = ByteBuffer.allocateDirect(n * 4);
bb.order(ByteOrder.nativeOrder());
FlaotBuffer fb = bb.asFloatBuffer();

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Pages: [1] 2
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Longarmx (52 views)
2014-10-17 03:59:02

Norakomi (43 views)
2014-10-16 15:22:06

Norakomi (33 views)
2014-10-16 15:20:20

lcass (38 views)
2014-10-15 16:18:58

TehJavaDev (68 views)
2014-10-14 00:39:48

TehJavaDev (68 views)
2014-10-14 00:35:47

TehJavaDev (60 views)
2014-10-14 00:32:37

BurntPizza (73 views)
2014-10-11 23:24:42

BurntPizza (45 views)
2014-10-11 23:10:45

BurntPizza (86 views)
2014-10-11 22:30:10
Understanding relations between setOrigin, setScale and setPosition in libGdx
by mbabuskov
2014-10-09 22:35:00

Definite guide to supporting multiple device resolutions on Android (2014)
by mbabuskov
2014-10-02 22:36:02

List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!