LWJGL Forum

Programming => Lightweight Java Gaming Library => Topic started by: Jens v.P. on October 24, 2007, 16:25:57

Title: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 24, 2007, 16:25:57
Hi,

since I'm using the immutable/mutable paradigm for avoiding unnecessary copy operations (and unexpected modifications), I'm using ReadableVector3f a lot. Now I've found that Vector3f's static operations, e.g. add(..), are expecting Vector3f parameters instead of ReadableVector3f, even if the parameter is only read.

I assume that these operations are using Vector3f for performance reasons. Or is this simply a bug? Is it possible to change the method signatures to add(ReadableVector3f, ReadableVector3f, Vector3f) (and the other methods respectively)? Or add new operations with these signatures?

Jens

PS: It's really a pity that Java doesn't support "const" as it is defined in C++ ...

PPS: Should Sourceforge's feature tracker be used instead of this forum? The trackers seem so empty...
Title: Re: static vector operations and ReadableVector3f
Post by: princec on October 25, 2007, 07:40:52
It's a design bug.

Cas :)
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 25, 2007, 08:08:35
OK, so maybe I should submit a bug to LWJGL's bug tracker?

Talking about the vector classes, the following methods could be moved to interface ReadableVector3f, since they do not change the state of a Vector3f:
- normalise(Vector3f)
- negate(Vector3f)

Jens
Title: Re: static vector operations and ReadableVector3f
Post by: Matzon on October 26, 2007, 08:30:05
The best way to get this fixed, is to submit a patch ;)
Using the forum is preferred.
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 26, 2007, 18:51:29
OK, here is the patch, based on version 2909.

I replaced Vector?f with ReadableVector?f in all static Vector?f methods where possible. I did not pulled up normalise(Vector?f) and negate(Vector?f) since the hierarchy of the ReadableInterfaces does not correspond with the hierarchy of the implementation. That is, ReadableVector3f extends ReadableVector2f, but Vector3f does not extend Vector2f.

Since I used a tool (Plug-In Poor Man's Quick Fix) for changing the access method within these functions (from foo.x to foo.getX()), I think everything should be ok.
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 26, 2007, 18:57:05
OK, even with a tool a fool is still a fool, so I added some test classes ensuring I didn't mixed something up. Do you have a test system with automatic tests? The file attached contains the three (JUnit 4) test classes testing the static methods (as far as I have changed them), if you want to add them to a some kind of test system.
Title: Re: static vector operations and ReadableVector3f
Post by: elias on October 26, 2007, 19:17:34
I just now noticed that the ReadableVector* types are in fact interfaces. Accessing interface methods is quite a bit slower than accessing classes, even though the methods themselves are abstract (because of the multiple inheritance with interfaces vs. inheritance with abstract classes). I'm not sure if we want/can convert the ReadableVector* types to be abstract classes instead of interfaces, though.

- elias
Title: Re: static vector operations and ReadableVector3f
Post by: Matzon on October 26, 2007, 19:43:19
I just made some quick benchmarks on this - and it showed a performance drop of about 2.1% (prob +/- 0.5%)
Title: Re: static vector operations and ReadableVector3f
Post by: Matzon on October 26, 2007, 20:09:57
sorry, that was 21,82%  :o

public class Benchmark {

/**
* @param args
*/
public static void main(String[] args) {
long time = System.nanoTime();

Vector3f vector = new Vector3f((float)Math.random(), (float)Math.random(), (float)Math.random());
for(int j=0; j<1000; j++) {
float result = 0;
for(int i=0; i<1000000; i++) {
result += Vector3f.dot(vector, vector);
vector.set(result, result, result);
}

//System.out.println(vector + " dot => " + result);
}

long time2 = (System.nanoTime() - time);
System.out.println("Time: " + time2 + ", " + (time2/1000/1000/1000) + "s" );
}
}
Title: Re: static vector operations and ReadableVector3f
Post by: Matzon on October 26, 2007, 20:16:25
With patch:
Time: 10157392350, 10s
Time: 10085646360, 10s

Without patch:
Time: 7884583934, 7s
Time: 7884842627, 7s

Java 1.6.0_03, Core 2 Duo E6750@3.2, Windows XP, Client VM

I tried running with Server VM:

Patched:
Time: 6681096061, 6s
Time: 6684694283, 6s

Unpatched:
Time: 6023337883, 6s
Time: 6007725283, 6s

Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 26, 2007, 21:38:53
This is why I asked in my first posting whether it's a design bug or a performance issue... BTW: Did you run the benchmark with j<1000? On my MacBook Pro (C2D 2,2 Ghz, OS X) I got 7(,8) seconds with j<10 and 78 seconds with j<100.

Anyway... I really think that ReadableVector?f should be used instead of Vector?f wherever possible. So I think Elias' idea of making ReadableVector?f an abstract class would solve the problem. The fields may be defined protected, so the static methods could directly access the fields, but they are not visible from outside the class hierarchy.

Luckily, the Vector classes are not derived from each other, so we do not run into problems. IMHO, the following modifications are to be made (R = ReadableVector, V = Vector, W = WriteableVector, R/V/W4 omitted here):

interface Vector                       -- was class
interface W2 extends Vector
interface W3 extends W2

abstract class R extends Vector         -- was interface
abstract class R2 extends R
abstract class R3 extends R2

class V2 extends R2 implements W2   -- was: extends V  ...
class V3 extends R3 implements W3   -- was: extends V  ...

This way, V2 .. V4 can access the fields x, y, z, w directly, from outside the class hierarchy final getters can be used.
The Vector class has gone, method length() is to be implemented in R (where it belongs since it's a read only method).
normalise() has to be declared in the interface now and has to be implemented in V2, V3, and V4 (which is the only trade-off here).

The results returned by "instance of" are not changed except W is now an instance of V (which seems ok since V becomes a kind of W0 interface). Could this cause problems?
The good thing is that V3 is not an instance of V2 in the current version. So we do not run into problems here.

If you do not object I can implement this in the next days and provide another patch here, too.

Jens


Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 26, 2007, 21:43:33
Thinking about it, there's no need to define R, R2, .. abstract. Defining a constructor with parameters enables immutable (i.e. readable) Vector classes.
Title: Re: static vector operations and ReadableVector3f
Post by: elias on October 27, 2007, 05:28:16
Looks ok, except that the fields are now protected and thus cannot be accessed from the outside directly (they're public correctly). How about keeping the fields in the concrete classes (Vector2f, Vector3f, Vector4f) and just have public abstract getX/Y/Z() in the ReadableVector* classes? Hopefully we keep the performance and keep the ability to access vector*f.x/y/z.

- elias
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 27, 2007, 12:55:54
If the fields are defined in Vector, the getters are to be declared abstract (and yet virtual) in ReadableVector. If the fields are defined in ReadableVector, the getters can be defined final (and setters in vector, too). From within the Vector hierarchy, direct field access is possible. So, operations requiring many accesses to the fields can be implemented in Vector and descendants, which may be defined by clients, too.

I assume there is a performance ordering:
- direct field access (fastet)
- final methods
- virtual methods (slowest)
We should measure the "distance" between these three methods and decide on the results.

Besides these considerations, another question is how the effect of using an immutable (i.e. readable) class can have a positive effect on the client's design and performance, e.g. by reducing the necessity to create copies. I could imagine that creating a single copy of a vector costs more performance than 20 accesses to virtual methods.

Last but not least: If the readable vectors cannot be used in vector operations, they seem pretty useless to me.

Jens




Title: Re: static vector operations and ReadableVector3f
Post by: princec on October 27, 2007, 13:58:26
At this stage I would rather worry about correctness of design rather than absolute performance; the hybrid JVM coming next year is effectively going to minimise the differences between server and client compilers.

Cas :)
Title: Re: static vector operations and ReadableVector3f
Post by: elias on October 28, 2007, 06:05:17
Making the fields protected breaks existing users of the vector utils, so let's try the abstract gettters in ReadableVector first, and see if we can't get the required performance from it.

- elias
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 28, 2007, 11:41:54
IMHO it should make no difference whether an abstract method is defined in an abstract class or in an interface. Thus, my last patch already implemented these abstract getters -- with less performance. The only way to improve the performance is by moving the fields (x,y,z) to the readable (and then abstract) classes and allowing direct access within the hierarchy by defining them abstract.

But you're right: Direct field access from outside the hierarchy would be not possible anymore... and that's indeed a problem.
Title: Re: static vector operations and ReadableVector3f
Post by: elias on October 28, 2007, 12:08:26
It does make a difference, any class can only inherit from one class, but can "inherit" multiple interfaces, and this can pose problems for a jvm. A simple interface->abstract class conversion did make a difference in our own vector stuff (which happens to be mostly stolen from lwjgl).

- elias
Title: Re: static vector operations and ReadableVector3f
Post by: Matthias on October 28, 2007, 12:40:05
simple get methods like this:
public float getX() { return x; }
are inlined by the VM is they are not overwritten in a subclass.

So making a ReadableVectorXf class with has fields and getters plus a writeable VectorXf class that extends the ReadableVectorXf class should get full performance.

The only question is the desired inheritance model - e.g. should Vector3f inherit Vector2f or not - the above model would prevent this.

But having Vector4f inherit from Vector3f can cause subtle bugs (e.g. Matrix.transform() for vectors and points). Also some methods are defined only for 3 components and not for 4 - like normalize() when w=1.0f is assumed.

My proposal:
public class ReadableVector3f {
  float x,y,z;
  public float getX() { return x; }
  ...
}
public class Vector3f extends ReadableVector3f {
  public Vector3f setX(float x); { this.x=x; return this; }
}


Ciao Matthias
Title: Re: static vector operations and ReadableVector3f
Post by: princec on October 28, 2007, 13:26:24
Maths doesn't fit very well with the "hierarchy" of vectors. A Vector3f is not actually a Vector2f in reality. In fact you could reasonably argue a Vector2f is actually derived from Vector3f, it's just that z == 0.

Cas :)
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 29, 2007, 13:13:44
I've done some benchmarks. The program with source code is attached to this posting. The goal of this benchmark is to measure the performance of different design alternatives for implementing readable and writable vector classes.

The Test

Tested were two classes (or interfaces) implementing a Vector3f version. The first class (or interface) provides a read-only access while the second read-write access. Additional, a static method "add" is introduced as implemented in the current Vector3f class.
Three different design alternatives were implemented:

Concrete: The first one (called "Concrete") implements a concrete readable class with final getters and protected fields. The writable class extends this class providing setters.

Abstract: The second version (called "Abstract") implements an abstract readable class with abstract getters and no fields. The writable class extends this class, defining the fields (public) and implementing final getters and setters.

Interface: The third version (called "Interface") defines a read-only interfaces with getters, the writable class implements this interface and defines all fields (public) and final getters and setters.

The static add method has a similar signature in all three versions: left and right vector are passed as read-only instances, the result is a writable instance. If the passed result is null, a temporary variable is constructed and returned, otherwise the result is filled.

The benchmark was executed 4 times, two times with a server VM and two times with a client VM. For each VM, 1000000 calls of the add method and a setter were repeated 100 and 1000 times. The result are shown below and illustrated in the image.

The Result

The last version using interfaces is the slowest one in all tests. Using an abstract class is more or less as fast as using a concrete readable class. Interestingly it is even sometimes a little bit slower (which I didn't expected)!
Also surprising (at least for me) is the fact that in some tests (with the server VM) creating new temporary instances (i.e. passing null as result parameter) is faster then using an in-out parameter.
The server VM is in all tests much faster then the client VM. in some cases about 5 times. Even the slowest server tests is nearly as fast as the fastest client test!

Conclusion

Using abstract readable base classes is the winner. It doesn't change the current access possibilities, i.e. accessing the fields of a vector directly or by using the getter. It is just as fast as using concrete classes. So, I think the optimal design would look like the one I submitted above in this thread, except that the readable classes become abstract and that the fields are defined as public attributes in the vector classes. On the other hand, using interfaces (which is the clear looser of this benchmark) may be the most flexible design. IMHO vector classes are like "native" types for 3D programming and an interface is seldom needed, so I personally prefer using abstract classes.

Remarks

I was really surprised by the results. I didn't expect the server VM to perform so much faster. What surprised me most is that in some cases using abstract classes (and getters) is even faster then using a concrete class.
elias did already suggested using abstract classes and he is right! Maybe I should have trusted him in the first place, but it was still interesting doing this benchmark. princec pointed out that we have to worry about design more then performance since new (and better) VM versions will be available in the near future. I think he is right, too. And even if I declared "abstract" classes as the winner, using interfaces may be a better design decision because it is more flexible. On the other hand, using abstract classes was 1.3 to 2 times faster then interfaces in the nearly all cases (except one weird exception), so I think we should use that design.
Since I added the classes (and the sources) you may try the benchmark on your machine. Since the performance of the VM is so important, I'm curious about your result.

Since I'm not an expert on benchmarks, nor on VMs, nor on 3D, I wouldn't be surprised if you find some design errors in the benchmark or in my argumentation  :-)


The Numbers

$ java -server -jar benchmark.jar
Run benchmark, times: 100, count: 1000000
JRE 1.5.0_07 (Apple Computer, Inc.), Java HotSpot(TM) Server VM 1.5.0_07-87 ("Apple Computer, Inc.") on Mac OS X 10.4.10 (i386)
Concrete, create temp : 1,04 sec (1.040.273.000)         
Concrete, pass result : 0,51 sec (511.121.000) 
Abstract, create temp : 1,05 sec (1.052.720.000)
Abstract, pass result : 1,13 sec (1.126.970.000)
Interface, create temp: 1,04 sec (1.039.608.000)
Interface, pass result: 2,25 sec (2.248.686.000)

$ java -server -jar benchmark.jar 1000
Run benchmark, times: 1000, count: 1000000
JRE 1.5.0_07 (Apple Computer, Inc.), Java HotSpot(TM) Server VM 1.5.0_07-87 ("Apple Computer, Inc.") on Mac OS X 10.4.10 (i386)
Concrete, create temp : 10,84 sec (10.835.785.000)
Concrete, pass result : 5,04 sec (5.038.323.000)
Abstract, create temp : 10,51 sec (10.511.653.000)
Abstract, pass result : 9,64 sec (9.636.774.000)
Interface, create temp: 13,67 sec (13.669.681.000)
Interface, pass result: 22,45 sec (22.450.686.000)

$ java -client -jar benchmark.jar
Run benchmark, times: 100, count: 1000000
JRE 1.5.0_07 (Apple Computer, Inc.), Java HotSpot(TM) Client VM 1.5.0_07-87 ("Apple Computer, Inc.") on Mac OS X 10.4.10 (i386)
Concrete, create temp : 5,02 sec (5.024.559.000)
Concrete, pass result : 1,80 sec (1.797.204.000)
Abstract, create temp : 4,44 sec (4.444.387.000)
Abstract, pass result : 1,86 sec (1.864.297.000)
Interface, create temp: 6,87 sec (6.873.674.000)
Interface, pass result: 4,33 sec (4.327.192.000)

$ java -client -jar benchmark.jar 1000
Run benchmark, times: 1000, count: 1000000
JRE 1.5.0_07 (Apple Computer, Inc.), Java HotSpot(TM) Client VM 1.5.0_07-87 ("Apple Computer, Inc.") on Mac OS X 10.4.10 (i386)
Concrete, create temp : 51,42 sec (51.422.792.000)
Concrete, pass result : 18,05 sec (18.049.931.000)
Abstract, create temp : 50,21 sec (50.210.897.000)
Abstract, pass result : 18,46 sec (18.464.446.000)
Interface, create temp: 67,97 sec (67.965.217.000)
Interface, pass result: 43,23 sec (43.227.655.000)
Title: Re: static vector operations and ReadableVector3f
Post by: elias on October 29, 2007, 17:53:10
I'm not sure the abstract class implementatio is the winner:


$ java -server -jar benchmark.jar 1000
Run benchmark, times: 1000, count: 1000000
JRE 1.5.0_07 (Apple Computer, Inc.), Java HotSpot(TM) Server VM 1.5.0_07-87 ("Apple Computer, Inc.") on Mac OS X 10.4.10 (i386)
Concrete, pass result : 5,04 sec (5.038.323.000)
Abstract, pass result : 9,64 sec (9.636.774.000)


In this case, concrete seems quite a bit faster than abstract.

- elias
Title: Re: static vector operations and ReadableVector3f
Post by: princec on October 30, 2007, 12:43:33
As I understand it the Apple VM has no "server" VM - when you pass -server on the command line it merely alters some default memory and garbage collection parameters. There is no optimising compiler.

Can anyone clarify this?

Cas :)
Title: Re: static vector operations and ReadableVector3f
Post by: ndhb on October 30, 2007, 21:27:16
Quote from: jpilgrim on October 29, 2007, 13:13:44
Since I added the classes (and the sources) you may try the benchmark on your machine. Since the performance of the VM is so important, I'm curious about your result.

The results I am getting supports another conclusion. Using the client VM, abstract seems to be the slowest option.

Run benchmark, times: 100, count: 1000000
JRE 1.6.0_02 (Sun Microsystems Inc.), Java HotSpot(TM) Server VM 1.6.0_02-b06 (Sun Microsystems Inc.) on Windows Vista 6.0 (x86)
Concrete, create temp : 1,12 sec (1.121.485.831)
Concrete, pass result : 0,94 sec (935.372.792)
Abstract, create temp : 1,10 sec (1.103.308.661)
Abstract, pass result : 0,93 sec (933.168.042)
Interface, create temp: 1,06 sec (1.061.411.157)
Interface, pass result: 3,76 sec (3.758.560.097)

Run benchmark, times: 1000, count: 1000000
JRE 1.6.0_02 (Sun Microsystems Inc.), Java HotSpot(TM) Server VM 1.6.0_02-b06 (Sun Microsystems Inc.) on Windows Vista 6.0 (x86)
Concrete, create temp : 10,78 sec (10.775.736.861)
Concrete, pass result : 9,29 sec (9.290.345.002)
Abstract, create temp : 10,66 sec (10.656.240.947)
Abstract, pass result : 9,36 sec (9.359.918.090)
Interface, create temp: 10,50 sec (10.504.635.086)
Interface, pass result: 34,40 sec (34.400.562.362)

Run benchmark, times: 100, count: 1000000
JRE 1.6.0_02 (Sun Microsystems Inc.), Java HotSpot(TM) Client VM 1.6.0_02-b06 (Sun Microsystems Inc.) on Windows Vista 6.0 (x86)
Concrete, create temp : 2,03 sec (2.030.250.493)
Concrete, pass result : 0,77 sec (767.155.044)
Abstract, create temp : 6,72 sec (6.717.633.386)
Abstract, pass result : 4,67 sec (4.666.612.656)
Interface, create temp: 2,71 sec (2.710.840.903)
Interface, pass result: 1,49 sec (1.491.753.003)

Run benchmark, times: 1000, count: 1000000
JRE 1.6.0_02 (Sun Microsystems Inc.), Java HotSpot(TM) Client VM 1.6.0_02-b06 (Sun Microsystems Inc.) on Windows Vista 6.0 (x86)
Concrete, create temp : 20,40 sec (20.400.974.984)
Concrete, pass result : 7,69 sec (7.692.924.101)
Abstract, create temp : 64,07 sec (64.073.280.822)
Abstract, pass result : 39,62 sec (39.621.325.894)
Interface, create temp: 26,89 sec (26.885.917.344)
Interface, pass result: 15,73 sec (15.729.212.791)

kind regards,
Nicolai de Haan Brøgger
Title: Re: static vector operations and ReadableVector3f
Post by: princec on October 31, 2007, 10:50:18
Conclusion: get the design correct; then send the benchmarks off to Sun and ask them to make the best design the fastest too.

Cas :)
Title: Re: static vector operations and ReadableVector3f
Post by: Matzon on October 31, 2007, 11:00:36
When did you get all enterprisey on us :-*
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 31, 2007, 13:05:50
So, since it seems that it's the VM that matters, what will you (the LWJGL masters) implement? And when, i.e. in which LWJGL version?
Title: Re: static vector operations and ReadableVector3f
Post by: elias on October 31, 2007, 13:27:05
I vote for "Concrete", since it seems faster in all cases if run for long enough.

- elias
Title: Re: static vector operations and ReadableVector3f
Post by: Jens v.P. on October 31, 2007, 14:10:11
I would vote for concrete, too. But it may break existing applications (since the fields x, y, and z are moved from Vector to Readable and must become protected).