z.e.r.o
Jr. Member   Posts: 87
Java games rock!
|
 |
«
on:
2004-10-03 04:17:33 » |
|
Ok, to make a long story short: my library has a 3D system built on top of Lookup Tables and another built on to op real time trig calculations.
At the moment I have the two trunks separated in two different packages that does the same things, even if differently. They works, they have the same signature so I began wondering if a interface-based system will be better.
Let me explain better:
Instead of having two Vector3f classes in two different packages, we can do this:
interface Vector3f
//lookup table implementation class Vector3fLT implements Vector3f
//real time implementation class Vector3fRT implements Vector3f
This way generic 3D handling classes like transformations or Phisics engines can work with the interface (Vector3f) and ignore the implementation, allowing the programmers to use a mixed set of Vectors and 3d entities that relies on LT or RT calculations.
The problem is: using interfaces on a such critical part of the engine is a major overhead? Or is just nice? I know that using lots of inheritance can hinder perfomances, but what about interfaces?
|
|
|
|
Abuse
JGO Kernel      Posts: 1866 Medals: 5
falling into the abyss of reality
|
 |
«
Reply #1 on:
2004-10-03 05:53:15 » |
|
If the jvm doesn't optimise away interface usage such as that, we're all doomed!
|
|
|
|
|
abies
Sr. Member   Posts: 456
|
 |
«
Reply #2 on:
2004-10-03 07:56:43 » |
|
It cannot optimize it fully, because every Vector3f can be of different kind, even in same array you are iterating over. This makes chances of code inlining quite small without branches and with branches you probably already will give up a lot of benefit gained by lookup tables.
I think that precision you need should be decided in computation code, not at vector creation time. I would suggest doing two similar methods in same class with different postfixes and calling one which is more appopriate in given piece of code.
|
Artur Biesiadowski
|
|
|
Games published by our own members! Go get 'em!
|
|
Orangy Tang
JGO Kernel      Posts: 2959 Medals: 37
Monkey for a head
|
 |
«
Reply #3 on:
2004-10-03 08:53:46 » |
|
Hmm.. I'm just giving my particle system a bit of an overhaul, and adding a bunch of extra interfaces. Given that we're talking about lots of particles (so lots of calls) per frame, is there anything to watch for so the overhead is minimal?
|
|
|
|
z.e.r.o
Jr. Member   Posts: 87
Java games rock!
|
 |
«
Reply #4 on:
2004-10-03 14:46:38 » |
|
Tang, I think the only solution is to try and to compare our approaches...
Delving around in google got me pretty different opinions about the matter. Maybe the best solution is just to replicate the trunks... dunno.
Tomorrow evening I will run some benches.
|
|
|
|
cep21
Jr. Member   Posts: 86
Java games rock!
|
 |
«
Reply #5 on:
2004-10-05 13:11:39 » |
|
I created a IVector2f interface with two Fast2f and Slow2f classes, as well as a third Switch2f class. The Switch2f class has an extra boolean (fastSwitch) if statement inside the test function. Here were my results:
Total all Slow2f.getHash() : 563 Total all Slow2f from IVector2f.getHash() : 922 Total mixed slow/fast getHash() inside Interface : 952 Total switch getHash() : 596
It seems there is overhead having the interface figure out which function to call. An all Slow2f() array is actually faster than a half Slow2f() and a half Fast2f() array, if java has to figure out which function to call. It seems that it's faster to figure out the function yourself with a boolean "fastSwitch" inside a class, even if the switch changes very often. Anyone else have different results?
import java.util.Random;
/** * @author lindamoodj */ public class TestBenchmark {
private final static int N = 800; private final static int NUM_ITERATIONS = 12; private static IVector2f[][] vecsIS; private static IVector2f[][] vecsIM; private static Slow2f[][] vecsS; private static Switch2f[][] vecsSw; static long lastTime; static long slowTime; static long mixedTime; static long slowITime; static long switchTime; public static void main(String[] args) { for (int i=0;i<NUM_ITERATIONS;i++){ initVars(); loopInterface(); loopSlow(); loopMixed(); loopSwitch(); System.out.println(); } System.out.println(); System.out.println("Total all Slow2f.getHash() : " + slowTime); System.out.println("Total all Slow2f from IVector2f.getHash() : " + slowITime); System.out.println("Total mixed slow/fast getHash() inside Interface : " + mixedTime); System.out.println("Total switch getHash() : " + switchTime); }
private static void loopSlow() { System.out.println("getHash of all Slow2f()"); setTime(); // processArray(vecsS); processArraySlow(vecsS); slowTime+=fromTime(); System.out.println("The time taken:" + fromTime()); } private static void loopSwitch() { System.out.println("getHash of all Switch2f()"); setTime(); // processArray(vecsS); processArraySwitch(vecsSw); switchTime+=fromTime(); System.out.println("The time taken:" + fromTime()); } private static void loopMixed() { System.out.println("getHash of mixed slow and fast inside Interface"); setTime(); processArray(vecsIM); mixedTime+=fromTime(); System.out.println("The time taken:" + fromTime()); }
private static void loopInterface() { System.out.println("getHash of all Slow2f() inside interface"); setTime(); processArray(vecsIS); slowITime+=fromTime(); System.out.println("The time taken:" + fromTime()); }
private static void initVars() { vecsIS=new IVector2f[N][]; vecsS=new Slow2f[N][]; vecsIM=new IVector2f[N][]; vecsSw=new Switch2f[N][]; Random r=new Random(); for (int i=0;i<N;i++){ vecsIS=new IVector2f[N]; vecsS=new Slow2f[N]; vecsIM=new IVector2f[N]; vecsSw=new Switch2f[N]; for (int j=0;j<N;j++){ float x=r.nextFloat(); float y=r.nextFloat(); vecsIS[j]=new Slow2f(x,y); vecsS[j]=new Slow2f(x,y); vecsSw[j]=new Switch2f(x,y); if (i+j%2==0) vecsIM[j]=new Slow2f(x,y); else vecsIM[j]=new Fast2f(x,y); } } } private static void processArray(IVector2f[][] a){ float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a.length;j++) sum+=a[j].getHash(); System.out.println("The sum is " + sum); } private static void processArraySwitch(Switch2f[][] a) { float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a.length;j++){ Switch2f.fastSwitch=(j%2==0); sum+=a[j].getHash(); } System.out.println("The sum is " + sum); } private static void processArraySlow(Slow2f[][] a){ float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a.length;j++) sum+=a[j].getHash(); System.out.println("The sum is " + sum); }
private static void setTime(){ lastTime=System.currentTimeMillis(); } private static long fromTime(){ return System.currentTimeMillis()-lastTime; } private static interface IVector2f{ void set(float x,float y); float getHash(); } private static class Slow2f implements IVector2f{ float x,y; public Slow2f(float x,float y) { set(x,y); }
public void set(float x, float y) { this.x=x; this.y=y; }
public float getHash() { return x+y; } }
private static class Switch2f implements IVector2f{ float x,y; public static boolean fastSwitch; public Switch2f(float x,float y) { set(x,y); }
public void set(float x, float y) { this.x=x; this.y=y; }
public float getHash() { if (fastSwitch) return 1.0f; else return x+y; } }
private static class Fast2f implements IVector2f{ float x,y; public Fast2f(float x, float y) { set(x,y); } public void set(float x, float y) { this.x=x; this.y=y; }
public float getHash() { return 1.0f; } } }
|
|
|
|
|
z.e.r.o
Jr. Member   Posts: 87
Java games rock!
|
 |
«
Reply #6 on:
2004-10-07 02:29:44 » |
|
Your example is correct, there's a huge performance penalty when using interfaces, a performance hit that I've never thought it will be, this make me wonder if I have to rethink some aspects of basilisk to gain non-marginal performance. For example every object that has an "update(long elapsed)" method implements Updatable interface and every objects that have to perform looping functionalities implements Loopable.
This also means that every sprite implements Updatable. Usually you refer to a Sprite with his own abstract Sprite class (overloaded then in AnimatedSprite and StaticSprite) or its derived class straightly so it's not a problem but in a more complex game architecture you can use a round robin loop to update every updatable (sprites, vectors, even network code and so on) and this HAS heavy performance hits...
So, for 3D I have to think for a more clean and direct approach, in general, I've to think seriously about the interface-based approach, the problem never arose because most of my customers are developing not so demanding games (web games or small shareware titles, mostly puzzles), but indeed is a matter to be taken in serious consideration.
|
|
|
|
jherber
Jr. Member   Posts: 57
Java games rock!
|
 |
«
Reply #7 on:
2004-10-14 11:32:42 » |
|
your conclusion may be valid, but i don't think you will get a high degree of accuracy timing something that probably takes less time to execute than the accuracy of the timer.
instead of:
for number of iterations: starttime() operation() endtime() ...
try: starttime() for number of iterations operation() endtime()
...
|
|
|
|
|
phazer
Full Member   Posts: 144
Come get some
|
 |
«
Reply #8 on:
2004-10-15 02:48:27 » |
|
I just want to point out that the server JVM is much better at optimizing interface code, so always use the -server JVM switch when benchmarking. Also use some warmup iterations, because it will take some time for Hotspot to figure out that it needs to create specialized code that calls the implementation directly.
|
|
|
|
cep21
Jr. Member   Posts: 86
Java games rock!
|
 |
«
Reply #9 on:
2004-10-15 10:26:47 » |
|
With -server option, iterating each iteration 10 times, and removing all print statements except the last 4 I get the following:
Total all Slow2f.getHash() : 3814 Total all Slow2f from IVector2f.getHash() : 8219 Total mixed slow/fast getHash() inside Interface : 7749 Total switch getHash() : 4220
-server seems to be able to switch interfaces a bit better but is still slower overall than not using an interface.
Warming up seems to help each proportionally the same.
|
|
|
|
|
Games published by our own members! Go get 'em!
|
|
cep21
Jr. Member   Posts: 86
Java games rock!
|
 |
«
Reply #10 on:
2004-10-15 10:29:34 » |
|
Here is my modified code that removes the prints, allows it to warm up iterations before timing, and loops each iteration 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
| import java.util.Random;
public class TestBenchmark { private final static int N = 800; private final static int NUM_ITERATIONS = 12; private static IVector2f[][] vecsIS; private static IVector2f[][] vecsIM; private static Slow2f[][] vecsS; private static Switch2f[][] vecsSw; static long lastTime; static long slowTime; static long mixedTime; static long slowITime; static long switchTime; private static final int ITNUM = 10; public static void main(String[] args) { for (int i=0;i<NUM_ITERATIONS;i++){ initVars(); loopInterface(); loopSlow(); loopMixed(); loopSwitch(); if (i==1){ slowTime=mixedTime=slowITime=switchTime=0; } } System.out.println(); System.out.println("Total all Slow2f.getHash() : " + slowTime); System.out.println("Total all Slow2f from IVector2f.getHash() : " + slowITime); System.out.println("Total mixed slow/fast getHash() inside Interface : " + mixedTime); System.out.println("Total switch getHash() : " + switchTime); } private static void loopSlow() { setTime(); for (int i=0;i<ITNUM;i++) processArraySlow(vecsS); slowTime+=fromTime(); } private static void loopSwitch() { setTime(); for (int i=0;i<ITNUM;i++) processArraySwitch(vecsSw); switchTime+=fromTime(); } private static void loopMixed() { setTime(); for (int i=0;i<ITNUM;i++) processArray(vecsIM); mixedTime+=fromTime(); } private static void loopInterface() { setTime(); for (int i=0;i<ITNUM;i++) processArray(vecsIS); slowITime+=fromTime(); } private static void initVars() { vecsIS=new IVector2f[N][]; vecsS=new Slow2f[N][]; vecsIM=new IVector2f[N][]; vecsSw=new Switch2f[N][]; Random r=new Random(); for (int i=0;i<N;i++){ vecsIS[i]=new IVector2f[N]; vecsS[i]=new Slow2f[N]; vecsIM[i]=new IVector2f[N]; vecsSw[i]=new Switch2f[N]; for (int j=0;j<N;j++){ float x=r.nextFloat(); float y=r.nextFloat(); vecsIS[i][j]=new Slow2f(x,y); vecsS[i][j]=new Slow2f(x,y); vecsSw[i][j]=new Switch2f(x,y); if (i+j%2==0) vecsIM[i][j]=new Slow2f(x,y); else vecsIM[i][j]=new Fast2f(x,y); } } } private static void processArray(IVector2f[][] a){ float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a[i].length;j++) sum+=a[j][i].getHash(); } private static void processArraySwitch(Switch2f[][] a) { float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a[i].length;j++){ Switch2f.fastSwitch=!Switch2f.fastSwitch; sum+=a[j][i].getHash(); } } private static void processArraySlow(Slow2f[][] a){ float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a[i].length;j++) sum+=a[j][i].getHash(); } private static void setTime(){ lastTime=System.currentTimeMillis(); } private static long fromTime(){ return System.currentTimeMillis()-lastTime; } private static interface IVector2f{ void set(float x,float y); float getHash(); } private static class Slow2f implements IVector2f{ float x,y; public Slow2f(float x,float y) { set(x,y); } public void set(float x, float y) { this.x=x; this.y=y; } public float getHash() { return x+y; } } private static class Switch2f implements IVector2f{ float x,y; public static boolean fastSwitch; public Switch2f(float x,float y) { set(x,y); } public void set(float x, float y) { this.x=x; this.y=y; } public float getHash() { if (fastSwitch) return 1.0f; else return x+y; } } private static class Fast2f implements IVector2f{ float x,y; public Fast2f(float x, float y) { set(x,y); } public void set(float x, float y) { this.x=x; this.y=y; } public float getHash() { return 1.0f; } } } |
|
|
|
|
|
phazer
Full Member   Posts: 144
Come get some
|
 |
«
Reply #11 on:
2004-10-15 12:32:22 » |
|
My results using JDK 1.5.0 -server:
Total all Slow2f.getHash() : 3985 Total all Slow2f from IVector2f.getHash() : 7742 Total mixed slow/fast getHash() inside Interface : 7741 Total switch getHash() : 4206
Commenting out the line "loopInterface();" gives the following results:
Total all Slow2f.getHash() : 4065 Total all Slow2f from IVector2f.getHash() : 0 Total mixed slow/fast getHash() inside Interface : 4648 <<<< Total switch getHash() : 4215
This result is incorrect and fixed by changing the line "if (i+j%2==0)" in initVars() to "if (r.nextBoolean())".
Commenting out just "loopMixed();" gives the following results:
Total all Slow2f.getHash() : 4068 Total all Slow2f from IVector2f.getHash() : 4033 Total mixed slow/fast getHash() inside Interface : 0 Total switch getHash() : 4298
Which indicates that because Fast2f.getHash() is never called, Hotspot can now figure out which implementation of IVector2f.getHash() to use (Slow2f.getHash()).
I'm a bit surprised that the switching implementation is much faster than using the interface arrays. Hotspot should be able to optimize the instance check to just as efficient code.
|
|
|
|
cep21
Jr. Member   Posts: 86
Java games rock!
|
 |
«
Reply #12 on:
2004-10-15 13:53:15 » |
|
Damn order of operations!  I was supprised as well at the speed of the SwitchVector class. An explanation would be very interesting as to why it's faster and what could be done to make the interface implimentation as fast.
|
|
|
|
|
cep21
Jr. Member   Posts: 86
Java games rock!
|
 |
«
Reply #13 on:
2004-10-15 14:16:43 » |
|
Here's something that's kind of trippy. If I use instanceof and cast, my results are faster than just letting the interface figure out which function to call on its own. Here is the new code (with your boolean fix) and my results: Total all Slow2f.getHash() : 3407 Total all Slow2f from IVector2f.getHash() : 6735 Total mixed slow/fast getHash() inside Interface : 6795 Total switch getHash() : 3125 Total cast getHash() : 4375 The total sum is 383990368 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
| import java.util.Random;
public class TestBenchmark { private final static int N = 800; private final static int NUM_ITERATIONS = 12; private static IVector2f[][] vecsIS; private static IVector2f[][] vecsIC; private static IVector2f[][] vecsIM; private static Slow2f[][] vecsS; private static Switch2f[][] vecsSw; static long lastTime; static long slowTime; static long mixedTime; static long slowITime; static long switchTime; static long castTime; static long sumT; private static final int ITNUM = 10; public static void main(String[] args) { for (int i=0;i<NUM_ITERATIONS;i++){ initVars(); loopInterface(); loopSlow(); loopMixed(); loopSwitch(); loopCast(); if (i==1){ castTime=slowTime=mixedTime=slowITime=switchTime=0; } } System.out.println(); System.out.println("Total all Slow2f.getHash() : " + slowTime); System.out.println("Total all Slow2f from IVector2f.getHash() : " + slowITime); System.out.println("Total mixed slow/fast getHash() inside Interface : " + mixedTime); System.out.println("Total switch getHash() : " + switchTime); System.out.println("Total cast getHash() : " + castTime); System.out.println("The total sum is " + sumT); } private static void loopSlow() { setTime(); for (int i=0;i<ITNUM;i++) processArraySlow(vecsS); slowTime+=fromTime(); } private static void loopCast() { setTime(); for (int i=0;i<ITNUM;i++) processArrayCast(vecsIC); castTime+=fromTime(); } private static void loopSwitch() { setTime(); for (int i=0;i<ITNUM;i++) processArraySwitch(vecsSw); switchTime+=fromTime(); } private static void loopMixed() { setTime(); for (int i=0;i<ITNUM;i++) processArray(vecsIM); mixedTime+=fromTime(); } private static void loopInterface() { setTime(); for (int i=0;i<ITNUM;i++) processArray(vecsIS); slowITime+=fromTime(); } private static void initVars() { vecsIS=new IVector2f[N][]; vecsS=new Slow2f[N][]; vecsIM=new IVector2f[N][]; vecsSw=new Switch2f[N][]; vecsIC=new IVector2f[N][]; Random r=new Random(); for (int i=0;i<N;i++){ vecsIS[i]=new IVector2f[N]; vecsS[i]=new Slow2f[N]; vecsIM[i]=new IVector2f[N]; vecsSw[i]=new Switch2f[N]; vecsIC[i]=new IVector2f[N]; for (int j=0;j<N;j++){ float x=r.nextFloat(); float y=r.nextFloat(); vecsIS[i][j]=new Slow2f(x,y); vecsS[i][j]=new Slow2f(x,y); vecsSw[i][j]=new Switch2f(x,y); if (r.nextBoolean()){ vecsIM[i][j]=new Slow2f(x,y); vecsIC[i][j]=new Slow2f(x,y); } else{ vecsIM[i][j]=new Fast2f(x,y); vecsIC[i][j]=new Fast2f(x,y); } } } } private static void processArray(IVector2f[][] a){ float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a[i].length;j++) sum+=a[j][i].getHash(); sumT+=sum; } private static void processArrayCast(IVector2f[][] a){ float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a[i].length;j++){ if (a[j][i] instanceof Fast2f) sum+=((Fast2f)a[j][i]).getHash(); else sum+=((Slow2f)a[j][i]).getHash(); } sumT+=sum; } private static void processArraySwitch(Switch2f[][] a) { float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a[i].length;j++){ Switch2f.fastSwitch=!Switch2f.fastSwitch; sum+=a[j][i].getHash(); } sumT+=sum; } private static void processArraySlow(Slow2f[][] a){ float sum=0; for (int i=0;i<a.length;i++) for (int j=0;j<a[i].length;j++) sum+=a[j][i].getHash(); sumT+=sum; } private static void setTime(){ lastTime=System.currentTimeMillis(); } private static long fromTime(){ return System.currentTimeMillis()-lastTime; } private static interface IVector2f{ void set(float x,float y); float getHash(); } private static class Slow2f implements IVector2f{ float x,y; public Slow2f(float x,float y) { set(x,y); } public void set(float x, float y) { this.x=x; this.y=y; } public float getHash() { return x+y; } } private static class Switch2f implements IVector2f{ float x,y; public static boolean fastSwitch; public Switch2f(float x,float y) { set(x,y); } public void set(float x, float y) { this.x=x; this.y=y; } public float getHash() { if (fastSwitch) return 1.0f; else return x+y; } } private static class Fast2f implements IVector2f{ float x,y; public Fast2f(float x, float y) { set(x,y); } public void set(float x, float y) { this.x=x; this.y=y; } public float getHash() { return 1.0f; } } } |
|
|
|
|
|
z.e.r.o
Jr. Member   Posts: 87
Java games rock!
|
 |
«
Reply #14 on:
2004-10-25 02:03:22 » |
|
This thread has become quite interesting 
|
|
|
|
phazer
Full Member   Posts: 144
Come get some
|
 |
«
Reply #15 on:
2004-10-25 05:27:53 » |
|
Using 1
| if (a[j][i].getClass() == Fast2f.class) |
instead of 1
| if (a[j][i] instanceof Fast2f) |
seems to be a little faster on my machine. You can also try storing an int inside the base class that identified each sub class (won't work with an interface though) and switch on that int instead. Should be about as fast as the getClass() method though.
|
|
|
|
|