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 ):
EDIT version is RELEASE . I think this answers my question.
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?
source to share
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:
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.
source to share