Galandil
Galandil

Reputation: 4249

C# Singleton implementation performance

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

Answers (3)

Krikor Ailanjian
Krikor Ailanjian

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

Cade Roux
Cade Roux

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

hoodaticus
hoodaticus

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

Related Questions