Wednesday, September 5, 2012

Speed Test: Dot Operator and Empty Functions

For a component based class, if a component does NOT need updating,
should it call an empty update function, or should it have a variable
called "needsUpdate" that determines if an update method is called?

Class Test:

Component based object:
package com.JM.RESEARCH_CLASSES.functionOrFlag{
 
 import com.JM.RESEARCH_CLASSES.functionOrFlag.ModuleWithEmptyUpdateLocalFunction;
 import flash.utils.getTimer;
 
 public class TEST_FuncOrFlag {
  
  private var module1:ModuleWithEmptyUpdateLocalFunction;
  private var module2:ModuleWithEmptyUpdateLocalFunction;
  private var module3:ModuleWithEmptyUpdateLocalFunction;
  private var module4:ModuleWithEmptyUpdateLocalFunction;
  
  private var hasModule1:Boolean = true;
  private var hasModule2:Boolean = true;
  private var hasModule3:Boolean = true;
  private var hasModule4:Boolean = true;
  
  private var module3_updateLocal:Function;
  
  private var variableAccessTest:Boolean;
  
  
  private var module1NeedsUpdate:Boolean = false;
  
  
  private var loopTimes:int = 654321;
  private var ii:int;
  private var t1:int;
  private var t2:int;
  private var tt:int;

  public function TEST_FuncOrFlag() {
   module1 = new ModuleWithEmptyUpdateLocalFunction();
   module2 = new ModuleWithEmptyUpdateLocalFunction();
   module3 = new ModuleWithEmptyUpdateLocalFunction();
   module4 = new ModuleWithEmptyUpdateLocalFunction();
   
   module3_updateLocal = module3.updateLocal;
  }
  
  public function test():void
  {
   t1 = getTimer();
   for(ii=0; ii < loopTimes; ii++){
    if(hasModule1 && module1NeedsUpdate)
    {
     module1.updateLocal();
    }
   }
   t2 = getTimer();
   tt = t2-t1;
   trace("skipping function call time: " + tt);
   
   
   t1 = getTimer();
   for(ii=0; ii < loopTimes; ii++){
    if(hasModule2)
    {
     module2.updateLocal();
    }
   }
   t2 = getTimer();
   tt = t2-t1;
   trace("empty function call time: " + tt);
   
   
   t1 = getTimer();
   for(ii=0; ii < loopTimes; ii++){
    if(hasModule3)
    {
     module3_updateLocal(); //call bound function.
    }
   }
   t2 = getTimer();
   tt = t2-t1;
   trace("empty function call, bound function:" + tt);
   
   t1 = getTimer();
   for(ii=0; ii < loopTimes; ii++){
    if(hasModule4)
    {
     variableAccessTest = module4.testPublicVar;
    }
   }
   t2 = getTimer();
   tt = t2-t1;
   trace("public variable access:" + tt);
   
     t2 = getTimer();
   tt = t2-t1;
   trace("empty function call, bound function:" + tt);
   
   t1 = getTimer();
   for(ii=0; ii < loopTimes; ii++){
    if(true)
    {
     internalFunc();
    }
   }
   t2 = getTimer();
   tt = t2-t1;
   trace("internal function call:" + tt);
   
  }
  
  private function internalFunc():void{}; //empty function.

 }//class
}//package
//Module with empty update:
package  com.JM.RESEARCH_CLASSES.functionOrFlag{
 
 public class ModuleWithEmptyUpdateLocalFunction {
  
  public var testPublicVar:Boolean = true;

  public function ModuleWithEmptyUpdateLocalFunction() {
   // constructor code
  }
  
  public function updateLocal():void
  {
   //do nothing. This function needs not to be updated.
   //we are doing this to keep uniform architecture.
  }
 }
}

//Testing setup:
import com.JM.RESEARCH_CLASSES.functionOrFlag.TEST_FuncOrFlag;
var test:TEST_FuncOrFlag = new TEST_FuncOrFlag();
test.test();
Results:
skipping function call time:
20, 19, 20, 21

empty function call time:
126, 128, 137, 131


Explanation of results:
Making function call to empty function is slower
than checking a boolean that is PART of a class.

So we have:
Two Boolean checks(FASTER) 
vs
Using the DOT operator to access ANOTHER object.
AND calling a function.

UPDATE:
Trying out a bound function call to empty function.
The theory: It would be nice for the main "gameObject"
to have a single vector that stores all the objects
who's updateLocal() function need to be called.

If the module does NOT exist, or does not need to be updated,
it simply would not have it's update method included in the vector.

Results:
empty function call, bound function: 159, 155, 145, 144
A bit slower than calling an empty function.
Not surprising, as I already knew calling bound functions was a bit slow.

Earlier I had tried to do something like:
private var math_sqrt:Function = Math.sqrt;
And found that:
math_sqrt() is slower than Math.sqrt();


Judging by results:
I would say that for a particle system, it definitely makes sense
to use an MVC or Bridge pattern.
If you have 1000 particles, having ONE class that updates all the particles
positions is going to be ONE function call rather than 1000 slow function
calls to each self-updating particle.

Now... I need to do one more test.
To see if it is the function call, or simply the use of the dot-operator.

For example: When using an MVC model in a particle system,
you have to prepped all of your logic with a reference to the the
current object you are working on:

If working with self updating particles you can write:
vel_x += acc_x;

If working with an MVC model where there is an updater class for the particle
system:
currentParticle.vel_x += currentParticle.acc_x;

Question:
How slow is the dot operator accessing a VARIABLE compared to the
dot operator calling an EMPTY FUNCTION?

Need to do a quick test on this.

public variable access: 19, 20, 21, 21
Result: Pretty much as fast as accessing local variables.

FINAL WORDS:
I read this article that mentions that OOP is made to increase programmer efficiency, NOT code execution efficiency:

http://www.as3dp.com/2011/04/beginners-oop-design-patterns-in-actionscript-3-0-post-2-code-organizer/

This is a good point...  So....  Even though a bound function call is the SLOWEST...

I really think the design of having a que of modules in a vector that are all updated in a loop is quite elegant...

I think I am going to program my gameObject class this way.

I am being a bit ambitious thinking that my
game object class needs to be so efficient that
I could use instances of "gameObject" as particles in a particle system.

FINAL RESULTS: (I swear I am done now)
skipping function call time        : 20,  19,  20,  21
empty function call time           : 126, 128, 137, 131
empty function call, bound function: 159, 155, 145, 144
public variable access             : 19,  20,  21,  21
internal function call             :109 , 124, 125, 109

No comments:

Post a Comment