Terrarium: Enumerating collections with an outlook on how to speed up the process.
Okay, so I was going through some old source code, that I won't claim since I probably didn't write it based on the fact the as statement wasn't used (as soon as I found out about the as statement I made sure to take advantage of its usage whenever possible) and I found the following code. Note this is pretty old code, but I'll use it anyway.
OrganismState organismState = newWorldState.GetOrganismState(organismID);
if (organismState != null && organismState.GetType() == typeof(PlantState))
{
PlantState plantState = (PlantState) organismState;
int availableLight = CurrentVector.State.GetAvailableLight(plantState);
plantState.GiveEnergy(availableLight);
}
Okay, now this is a prime example of some code that should have used the as statement. So what is happening in the above code? Well, we are doing a null check, then we are doing a GetType() call, and check that versus a target type. After all this we perform a cast and finally start to work on the target. How could this code be simplified?
PlantState plantState = newWorldState.GetOrganismState(organismID) as PlantState;
if ( plantState != null )
{
int availableLight = CurrentVector.State.GetAvailableLight(plantState);
plantState.GiveEnergy(availableLight);
}
That simplifies things quite a bit and is a little faster. Unfortunately we have little pieces of the original all over the place in the code, so updating things becomes a hassle (assume that the original way was the only way to do it for some reason) whenever we change the way we perform certain operations (aka iterating over a collection of various objects where we only want specific ones based on a type). Here things like pre-sorting can greatly enhance the locality of the type checking operation, and at the same time allow you to quickly change the way sorting happens if your code changes a bit. It also allows you to prevent the type checking for the same item multiple times. This can be pretty powerful. Let's examine the collections we might use in order to simplify our design.
// We know there are Animals and Plants, both of which are derived from Organism
// so we are going to role all this data up early for use in all of our
// methods.
int animals = 0;
int plants = 0;
// Pre-count
for(int i = 0; i < State.Organisms.Length; i++)
{
if ( State.Organisms[i] is PlantState )
{
plants++;
}
else if ( State.Organisms[i] is AnimalState )
{
animals++;
}
}
OrganismState[] oStates = new OrganismState[State.Organisms.Length];
PlantState[] pStates = new PlantState[plants];
AnimalState[] aStates = new AnimalState[animals];
for(int i = 0, j = 0, k = 0; i < State.Organisms.Length; i++)
{
oStates[i] = (OrganismState) State.Organisms[i];
if ( State.Organisms[i] is PlantState )
{
pStates[j++] = (PlantState) State.Organisms[i];
}
else if ( State.Organisms[i] is AnimalState )
{
aStates[k++] = (AnimalState) State.Organisms[i];
}
}
I want to place a note here about something. Notice, I'm using casting instead of the as keyword. You see, the difference is that I'm already pre-checking the value using the is keyword. I don't want to incur the wrath of extra work (as does some extra work checking the type before casting) twice in this case. This demonstrates that depending on your casting process and where you need various pieces of information in order to make your casting decisions, you'll need to change which casting methods you use.
Now our original method changes a bit and becomes highly simplified. Rather than doing the casting and iterating over an array where up to half of the elements might not even match the correct type, we've already done the work of pre-sorting, so we can index into our plant state array directly.
for(int i = 0; i < pStates.Length; i++)
{
int availableLight = CurrentVector.State.GetAvailableLight(pStates[i]);
pStates[i].GiveEnergy(availableLight);
}
A final optimization has been pointed out several times in other blogs. There is no reason to incur the wrath of a local variable such as availableLight if we don't need it. It is possible to instead change the method GetAvailableLight to simply apply the lighting values directly to the plant state in question. I mean, why would we, when it simplifies the design process (taking away some flexibility). The final method might look something like:
for(int i = 0; i < pStates.Length; i++)
{
CurrentVector.State.GiveEnergyByLight(pStates[i]);
}
Only use this final optimization where it makes sense. Perhaps do some tuning and see if it actually buys you anything.