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

Introduction

So, I was making a game thinking about how I should structure and update all of my game objects. I ( case 1 ) creates a simple GameObj

as a parent class and puts some physics in the method virtual Update

, draws in by default virtual Draw

, etc., and makes every other object (wall, enemy, player ...) be a child, OR do I ( case 2 ) use components as described in this article . In short, the author explains that we can create interfaces for user input, physics updates, and drawing (let's focus on these 3) and describe ours GameObj

with preprogrammed instances of these interfaces.

Now in both cases I will get a class loop GameObj

.
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 like InputComponent.Update

and DrawComponent.Draw

(or CollisionComponent.Check(gameObj[x])

, I don't know).

The reasons given include the level level that takes care of all of our playable objects

Reasons to Considerif ( x != null )


In both cases, we might have a situation where we need to call if ( x != null )

. In case 1, we might not want to delete and add to gameObjList

all the time, but recycle the instances, so we set them to null

without doing anything along the lines gameObjList.Remove(x)

. In case 2, perhaps we want to be able to not install some components, so we would need to ask if (gameObjList[i].someComponent != null)

to be able to call gameObjList[i].someComponent.Update()

.


Reasons for Considering Calling an Empty Function
Also, in both cases, we could simply call an empty function (eg public void myFunction(){}

). Let's take a look at a self-explanatory class Wall

. He only exists to be there. The id is not updated, but has some relation to other GameObj

s. Also, some of them in case 1, like say, MovingWall

or Platform

, will have some kind of update. Regarding case 2, we can always declare a default empty class someComponent

whose function Update

will be empty and therefore an instance of that class will be set to our GameObj

component if not set in the constructor, Maybe something like this

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

    this.physicsComponent = physicsComponent;
}

      


Research
Now I have not found what would be the most efficient task in the game engine we are building here. Here are some examples I just tested (note some of them are for reference only):
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 I tested. You can run locally, change some 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 version is RELEASE . I think this answers my question.
excel chart version version



Questions
Q1. Which approach to use will be more effective?
Q2. In which case?
Q3. Is there any official documentation on this? Q4. Has anyone else tested this, maybe more intensely?
Q5. Is there a better way to test this (is my code corrupted)?
Q6. Is there a better way to solve the problem of huge lists of instances that need to be updated quickly and efficiently, like in every frame?
EDIT Q7. Why is the static method taking so much longer to execute on the release version?

+3


source to share


1 answer


As @ grek40 said, I did another test in which I called myClass.staticEmptyFunction();

100 times before starting the test so that it could be cashed out. I also set testTimesCount

at 10,000 and oneTestDuration

up to 1,000,000. Here are the results:enter image description here



It now looks much more stable. Even the small differences you may notice I blame my google chrome, limit and flood running in the background. The questions I asked, I asked because I thought there would be a big difference, but I think the optimization was much better than I expected. I am also guessing that no one did this because they probably knew there was C in there and that people were optimizing the optimization work.

0


source







All Articles