Reputation: 4249
I'm in the process of learning C#, after I quit programming since a long time (back in early '90, used to program in C and Assembler), so I'm pretty new to C# and OOP in general.
I stumbled upon this strange behaviour when studying the Singleton pattern and its implementation. I saw that some people use the if (==), while others use the if (!=) to check if the instance has already been created or not. I wondered if there was any significant performance difference between the two (from a logic point of view, they ofc work exactly the same), and after some testing I found out that the != is way faster than the == (between 12% and 22%!), and I can't really understand why.
I know that this implementation is not thread-safe btw, I wrote it just to try and get an answer to my curiosity, so please don't bash me about it. :)
So, anyone has an answer to this? Specifically, I'm interested in why this happens. This is the code I used:
Test Code:
using System;
using System.Diagnostics;
namespace SingletonSpeedTest
{
class Program
{
static void Main(string[] args)
{
bool wantToQuit = false;
while (!wantToQuit)
{
Console.Write("Enter number of cycles: ");
UInt64 cycles = Convert.ToUInt64(Console.ReadLine());
long avg1 = Singleton1.TestSingleton(cycles);
long avg2 = Singleton2.TestSingleton(cycles);
float perc = (float) (avg2 - avg1) / avg1 * 100;
Console.WriteLine("\nNumber of ticks in Singleton with == in if: " + avg1);
Console.WriteLine("Number of ticks in Singleton with != in if: " + avg2);
Console.WriteLine("Difference in percentage is " + perc + "%");
Console.Write("\nDo you want to quit? (y/n): ");
if (Console.ReadLine() == "y") wantToQuit = true;
}
}
}
}
Singleton1 Class with == in if:
using System;
using System.Diagnostics;
namespace SingletonSpeedTest
{
public sealed class Singleton1
{
private static Singleton1 instance = null;
private Singleton1() { }
public static Singleton1 Instance()
{
if (instance == null) instance = new Singleton1();
return instance;
}
public static long TestSingleton(UInt64 cycles)
{
Stopwatch sw = Stopwatch.StartNew();
for (UInt64 i = 0; i < cycles; i++)
{
Instance();
}
sw.Stop();
return sw.ElapsedTicks;
}
}
}
Singleton2 Class with != in if
using System;
using System.Diagnostics;
namespace SingletonSpeedTest
{
public sealed class Singleton2
{
private static Singleton2 instance = null;
private Singleton2() { }
public static Singleton2 Instance()
{
if (instance != null) return instance;
return instance = new Singleton2();
}
public static long TestSingleton(UInt64 cycles)
{
Stopwatch sw = Stopwatch.StartNew();
for (UInt64 i = 0; i < cycles; i++)
{
Instance();
}
sw.Stop();
return sw.ElapsedTicks;
}
}
}
Upvotes: 1
Views: 1626
Reputation: 1852
When the program is compiled, you can see that Singleton2 has a few extra instructions, but it branches directly to the end if it sees that the instance exists. This gives it the speed boost you are seeing.
Here is the IL code for the instance methods of the two classes:
Singleton1:
.method public hidebysig static class SingletonSpeedTest.Singleton1
Instance() cil managed
{
// Code size 33 (0x21)
.maxstack 2
.locals init ([0] bool V_0,
[1] class SingletonSpeedTest.Singleton1 V_1)
IL_0000: nop
IL_0001: ldsfld class SingletonSpeedTest.Singleton1 SingletonSpeedTest.Singleton1::'instance'
IL_0006: ldnull
IL_0007: ceq
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: brfalse.s IL_0017
IL_000d: newobj instance void SingletonSpeedTest.Singleton1::.ctor()
IL_0012: stsfld class SingletonSpeedTest.Singleton1 SingletonSpeedTest.Singleton1::'instance'
IL_0017: ldsfld class SingletonSpeedTest.Singleton1 SingletonSpeedTest.Singleton1::'instance'
IL_001c: stloc.1
IL_001d: br.s IL_001f
IL_001f: ldloc.1
IL_0020: ret
} // end of method Singleton1::Instance
Singleton2:
.method public hidebysig static class SingletonSpeedTest.Singleton2
Instance() cil managed
{
// Code size 37 (0x25)
.maxstack 2
.locals init ([0] bool V_0,
[1] class SingletonSpeedTest.Singleton2 V_1)
IL_0000: nop
IL_0001: ldsfld class SingletonSpeedTest.Singleton2 SingletonSpeedTest.Singleton2::'instance'
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: brfalse.s IL_0015
IL_000d: ldsfld class SingletonSpeedTest.Singleton2 SingletonSpeedTest.Singleton2::'instance'
IL_0012: stloc.1
IL_0013: br.s IL_0023
IL_0015: newobj instance void SingletonSpeedTest.Singleton2::.ctor()
IL_001a: dup
IL_001b: stsfld class SingletonSpeedTest.Singleton2 SingletonSpeedTest.Singleton2::'instance'
IL_0020: stloc.1
IL_0021: br.s IL_0023
IL_0023: ldloc.1
IL_0024: ret
} // end of method Singleton2::Instance
This was generated with the MS ILDASM tool (interpreted language disassembler).
Upvotes: 2
Reputation: 89661
This is my Singleton base class I've used for years:
public class SingletonBase<T> where T : class
{
static SingletonBase()
{
}
public static readonly T Instance =
typeof(T).InvokeMember(typeof(T).Name,
BindingFlags.CreateInstance |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic,
null, null, null) as T;
}
Upvotes: 1
Reputation: 3880
In case 1 you are doing a goto almost every time to skip past the call to operator new. In case 2 you only do a goto if you have not initialized the variable, i.e., once. Simply put - case 2 does less work per call.
Upvotes: 0