3D Interface Based System

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?

If the jvm doesn’t optimise away interface usage such as that, we’re all doomed!

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.

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?

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.

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[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();
    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[i].length;j++){
    Switch2f.fastSwitch=(j%2==0);
    sum+=a[j][i].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[i].length;j++)
    sum+=a[j][i].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;
         }
    

    }
    }

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.

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()

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.

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.

Here is my modified code that removes the prints, allows it to warm up iterations before timing, and loops each iteration


 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;
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();
  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);
  for (int i=0;i<ITNUM;i++)
        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);
  for (int i=0;i<ITNUM;i++)
        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();
  for (int i=0;i<ITNUM;i++)
        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();
  for (int i=0;i<ITNUM;i++)
        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[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();
//  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[i].length;j++){
    Switch2f.fastSwitch=!Switch2f.fastSwitch;
    sum+=a[j][i].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[i].length;j++)
    sum+=a[j][i].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;
  }
 }
}

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.

Damn order of operations! :slight_smile: 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.

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


 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[][] 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;
  }
 }
}


This thread has become quite interesting :slight_smile:

Using


if (a[j][i].getClass() == Fast2f.class)

instead of


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.