Checking if instance is null VS calling empty function (C#)

Viewed 548

Introduction

So I was making a game, thinking how do I structure and update all my game objects. Do I (case 1) create a simple GameObj as a parent class and put some physics in virtual Update method, some default drawing in virtual Draw, etc, and make every other object (wall, enemy, player...) be the child, OR do I (case 2) use components as described in this article. In short, the writer explains that we could make interfaces for user input, physics update and draw (lets stop at those 3) and describe our GameObj with preprogrammed instances of these interfaces.

Now, in both cases I will get a loop of GameObj class.
In case 1 it would probably look something like this

// in Update function of the level class
for(int i = 0; i < gameObjList.Count; i++)
{
    gameObjList[i].Update();
}

And in case 2, something like this

// in UpdatePhysics function of the level class
for(int i = 0; i < gameObjList.Count; i++)
{
    gameObjList[i].PhysicComponent.Update();
}

And so on (in case 2) for other interfaces such as InputComponent.Update and DrawComponent.Draw (or CollisionComponent.Check(gameObj[x]), I dunno).

Reasons listed are ment to be inside a level class that takes care of all of our game objects

Reasons to consider if ( x != null )
In both cases we (could) have a situation where we need to call if ( x != null ). In case 1 we maybe don't want to delete and add to the gameObjList all the time, but recycle the instances, so we set them to null without doing something along the lines of gameObjList.Remove(x). In case 2 maybe we want to be able not to set some of the components, so we'd have to ask if (gameObjList[i].someComponent != null) to be able to call gameObjList[i].someComponent.Update().


Reasons to consider calling empty function
Also in both cases, we could just call an empty function (e.g. public void myFunction(){}). Lets consider the self explanatory Wall class. It exists just to be there. Id doesn't update, but it does have a certain relation to other GameObjs. Also, some of it's children in case 1, like a lets say MovingWall or Platform would have some sort of update. As for case 2, we could always declare a default, empty class of someComponent whose Update function would be empty, and so an instance of this class would be set to our GameObj component if none is set in the constructor. Maybe something like this

public GameObj(IPhysicsComponent physicsComponent, ...){
    if(physicsComponent == null)
        physicsComponent = PhysicsComponent.Default;

    this.physicsComponent = physicsComponent;
}


Research
Now, I didn't find what would be the most efficient thing to do in a game engine we are building here. Here are some examples I just tested (note some of them are just for reference):
1. empty loop
2. empty function
3. if(x != null) x.empyFunction(); x is always null
4. x?.emptyFunction(); x is always null
5. if(x != null) x.empyFunction(); x is not null
6. x?.emptyFunction(); x is not null
7. myClass.staticEmptyFunction();

These 7 points are tested 100 000 times, 10 000 times. The code below is the code that I tested with. You can run in locally, change some of the static variables, and the result will appear in "result.txt" in the folder where you ran the program. Here is the code :

public enum TimeType
{
    emptyLoop = 1,
    loopEmptyFunction = 2,
    loopNullCheck = 3,
    loopNullCheckShort = 4,
    loopNullCheckInstanceNotNull = 5,
    loopNullCheckInstanceNotNullShort = 6,
    loopEmptyStaticFunction = 7
}

class myTime
{
    public double miliseconds { get; set; }
    public long ticks { get; set; }
    public TimeType type { get; set; }

    public myTime() { }
    public myTime(Stopwatch stopwatch, TimeType type)
    {
        miliseconds = stopwatch.Elapsed.TotalMilliseconds;
        ticks = stopwatch.ElapsedTicks;
        this.type = type;
    }

}

class myClass
{
    public static void staticEmptyFunction() { }
    public void emptyFunction() { }
}

class Program
{
    static List<myTime> timesList = new List<myTime>();
    static int testTimesCount = 10000;
    static int oneTestDuration = 100000;


    static void RunTest()
    {
        Stopwatch stopwatch = new Stopwatch();

        Console.Write("TEST ");

        for (int j = 0; j < testTimesCount; j++)
        {
            Console.Write("{0}, ", j + 1);
            myClass myInstance = null;


            // 1. EMPTY LOOP
            stopwatch.Start();
            for (int i = 0; i < oneTestDuration; i++)
            {

            }
            stopwatch.Stop();
            timesList.Add(new myTime(stopwatch, (TimeType)1));
            stopwatch.Reset();


            // 3. LOOP WITH NULL CHECKING (INSTANCE IS NULL)
            stopwatch.Start();
            for (int i = 0; i < oneTestDuration; i++)
            {
                if (myInstance != null)
                    myInstance.emptyFunction();
            }
            stopwatch.Stop();
            timesList.Add(new myTime(stopwatch, (TimeType)3));
            stopwatch.Reset();


            // 4. LOOP WITH SHORT NULL CHECKING (INSTANCE IS NULL)
            stopwatch.Start();
            for (int i = 0; i < oneTestDuration; i++)
            {
                myInstance?.emptyFunction();
            }
            stopwatch.Stop();
            timesList.Add(new myTime(stopwatch, (TimeType)4));
            stopwatch.Reset();


            myInstance = new myClass();

            // 2. LOOP WITH EMPTY FUNCTION
            stopwatch.Start();
            for (int i = 0; i < oneTestDuration; i++)
            {
                myInstance.emptyFunction();
            }
            stopwatch.Stop();
            timesList.Add(new myTime(stopwatch, (TimeType)2));
            stopwatch.Reset();


            // 5. LOOP WITH NULL CHECKING (INSTANCE IS NOT NULL)
            stopwatch.Start();
            for (int i = 0; i < oneTestDuration; i++)
            {
                if (myInstance != null)
                    myInstance.emptyFunction();
            }
            stopwatch.Stop();
            timesList.Add(new myTime(stopwatch, (TimeType)5));
            stopwatch.Reset();


            // 6. LOOP WITH SHORT NULL CHECKING (INSTANCE IS NOT NULL)
            stopwatch.Start();
            for (int i = 0; i < oneTestDuration; i++)
            {
                myInstance?.emptyFunction();
            }
            stopwatch.Stop();
            timesList.Add(new myTime(stopwatch, (TimeType)6));
            stopwatch.Reset();


            // 7. LOOP WITH STATIC FUNCTION
            stopwatch.Start();
            for (int i = 0; i < oneTestDuration; i++)
            {
                myClass.staticEmptyFunction();
            }
            stopwatch.Stop();
            timesList.Add(new myTime(stopwatch, (TimeType)7));
            stopwatch.Reset();
        }

        Console.WriteLine("\nDONE TESTING");
    }

    static void GetResults()
    {
        // SUMS
        double sum1t, sum2t, sum3t, sum4t, sum5t, sum6t, sum7t,
            sum1m, sum2m, sum3m, sum4m, sum5m, sum6m, sum7m;
        sum1t = sum2t = sum3t = sum4t = sum5t = sum6t = sum7t =
            sum1m = sum2m = sum3m = sum4m = sum5m = sum6m = sum7m = 0;

        foreach (myTime time in timesList)
        {
            switch (time.type)
            {
                case (TimeType)1: sum1t += time.ticks; sum1m += time.miliseconds; break;
                case (TimeType)2: sum2t += time.ticks; sum2m += time.miliseconds; break;
                case (TimeType)3: sum3t += time.ticks; sum3m += time.miliseconds; break;
                case (TimeType)4: sum4t += time.ticks; sum4m += time.miliseconds; break;
                case (TimeType)5: sum5t += time.ticks; sum5m += time.miliseconds; break;
                case (TimeType)6: sum6t += time.ticks; sum6m += time.miliseconds; break;
                case (TimeType)7: sum7t += time.ticks; sum7m += time.miliseconds; break;
            }
        }

        // AVERAGES
        double avg1t, avg2t, avg3t, avg4t, avg5t, avg6t, avg7t,
            avg1m, avg2m, avg3m, avg4m, avg5m, avg6m, avg7m;

        avg1t = sum1t / (double)testTimesCount;
        avg2t = sum2t / (double)testTimesCount;
        avg3t = sum3t / (double)testTimesCount;
        avg4t = sum4t / (double)testTimesCount;
        avg5t = sum5t / (double)testTimesCount;
        avg6t = sum6t / (double)testTimesCount;
        avg7t = sum7t / (double)testTimesCount;

        avg1m = sum1m / (double)testTimesCount;
        avg2m = sum2m / (double)testTimesCount;
        avg3m = sum3m / (double)testTimesCount;
        avg4m = sum4m / (double)testTimesCount;
        avg5m = sum5m / (double)testTimesCount;
        avg6m = sum6m / (double)testTimesCount;
        avg7m = sum7m / (double)testTimesCount;

        string fileName = "/result.txt";
        using (StreamWriter tr = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + fileName))
        {
            tr.WriteLine(((TimeType)1).ToString() + "\t" + avg1t + "\t" + avg1m);
            tr.WriteLine(((TimeType)2).ToString() + "\t" + avg2t + "\t" + avg2m);
            tr.WriteLine(((TimeType)3).ToString() + "\t" + avg3t + "\t" + avg3m);
            tr.WriteLine(((TimeType)4).ToString() + "\t" + avg4t + "\t" + avg4m);
            tr.WriteLine(((TimeType)5).ToString() + "\t" + avg5t + "\t" + avg5m);
            tr.WriteLine(((TimeType)6).ToString() + "\t" + avg6t + "\t" + avg6m);
            tr.WriteLine(((TimeType)7).ToString() + "\t" + avg7t + "\t" + avg7m);
        }
    }

    static void Main(string[] args)
    {
        RunTest();

        GetResults();

        Console.ReadLine();
    }
}




When I put all the data in excel and made a chart, it looked like this (DEBUG):
Excel chart




EDIT - RELEASE version. I guess this answers my question.
release version excel chart



The questions are
Q1. What approach to use to be more efficient?
Q2. In what case?
Q3. Is there official documentation on this?
Q4. Did anybody else test this, maybe more intensively?
Q5. Is there a better way to test this (is my code at fault)?
Q6. Is there a better way around the problem of huge lists of instances that need to be quickly and efficiently updated, as in - every frame?
EDIT Q7. Why does the static method take so much longer to execute in the release version?

1 Answers
Related