Lesson 5: Improving Performance

Using Decorator instead of inheritance

The Env class has a method called setShowStatus(boolean). When set to true, the Env3D window will display a status line indicating the frame rate (fps) of our running program. Env3D is rate locked at 60fps, meaning that it will not go over 60 frames per second. Ideally, all of our programs should run at around 60fps.

Turn it on now and you will notice that our simulation frame rate is much lower than 60fps. What could be causing the slow down? One of the reason could be that we have too many objects on screen.

If you still have the first version of our simulation software (from lesson 1), try running it with env.setShowStatus(true). You should notice that our old version is actually achieving a much higher frame rate than our new version! The parts of our program that was changed between the old and the new version is the use of inheritance. It seems like inheritance is actually making our program run slower.

If you think about it, it does make sense. When Env3D wants to access an object's x, y and z fields, if env3d cannot find the fields in the object itself, it must search the parent class. This search actually takes up cpu time and in this case have a real effect on performance!

If we cannot use inheritance, what can we use? Study the following Creature code and see if you can figure out what has changed:

import env3d.EnvObject;
import java.util.ArrayList;

public class Creature 
{
    private EnvObject envObject;
    
    /**
     * Constructor for objects of class Creature
     */
    public Creature(double x, double y, double z)
    {
        envObject = new EnvObject();
        setX(x);
        setY(y);
        setZ(z);
        setScale(1);
    }

    /**
     * Move the creature randomly
     */
    public void move(ArrayList<Creature> creatures, ArrayList<Creature> dead_creatures)
    {                
        double rand = Math.random();
        if (rand < 0.25) {
            setX(getX()+getScale());
        } else if (rand < 0.5) {
            setX(getX()-getScale());
        } else if (rand < 0.75) {
            setZ(getZ()+getScale());
        } else if (rand < 1) {
            setZ(getZ()-getScale());
        }                
        
        if (getX() < getScale()) setX(getScale());
        if (getX() > 50-getScale()) setX(50 - getScale());
        if (getZ() < getScale()) setZ(getScale());
        if (getZ() > 50-getScale()) setZ(50 - getScale());
    }        

        
    public void setX(double value) 
    {
        envObject.setX(value);
    }

    public void setY(double value) 
    {
        envObject.setY(value);
    }
    
    public void setZ(double value) 
    {
        envObject.setZ(value);
    }

    public void setScale(double value) 
    {
        envObject.setScale(value);
    }
    
    public void setRotateY(double ry) 
    {
        envObject.setRotateY(ry);
    }
    
    public void setTexture(String t) 
    {
        envObject.setTexture(t);
    }

    public void setModel(String m) 
    {
        envObject.setModel(m);
    }
    
    public double getX() 
    {
        return envObject.getX();
    }
    
    public double getY() 
    {
        return envObject.getY();
    }
    
    public double getZ() 
    {
        return envObject.getZ();
    }
    
    public double getScale() 
    {
        return envObject.getScale();
    }
    
    public String getTexture() 
    {
        return envObject.getTexture();
    }
    
    public String getModel()
    {
        return envObject.getModel();
    }
    
    public EnvObject getEnvObject()
    {
        return envObject;
    }

    public double distance(Creature c) 
    {
        return envObject.distance(c.envObject);
    }
}

In this new version of Creature, we are making the Creature contain an EnvObject instead of inheriting from it. All of the mutators and accesors simply pass the task to the internal !envObject. We further modify our code so that whenever we want to add or remove a Creature into the environment, we use the following code:

        // Add all the animals into to the environment for display
        for (Creature c : creatures) {
            env.addObject(c.getEnvObject());
        }
        // Clean up of the dead tuxes.
        for (Creature c : dead_creatures) {
            env.removeObject(c.getEnvObject());
            creatures.remove(c);
        }

This way, we are adding the most basic form of EnvObject into our environment, and we do not take the performance hit of inheritance. The rest of our code is exactly the same as before.

Exercise

  1. The EnvNode class is located in the env3d.advanced package and has the same interface as EnvObject.  In fact, EnvNode can be used as a drop-in replacement for EnvObject.  Experiment with both classes to find out the difference between EnvObject and EnvNode?