I've been sitting on this idea for quite a long time and would like to hear what you guys think about it.
The standard idiom for writing a singleton is roughly as follows:
public class A {
...
private static A _instance;
public static A Instance() {
if(_instance == null) {
_instance = new A();
}
return _instance;
}
...
}
Here I'm proposing another solution:
public class A {
...
private static A _instance;
public static A Instance() {
try {
return _instance.Self();
} catch(NullReferenceExceptio) {
_instance = new A();
}
return _instance.Self();
}
public A Self() {
return this;
}
...
}
The basic idea behind it is that the runtime cost of 1 dereference and unthrown exception is lesser than that of one null check. I've tried to measure the potentional performance gain and here are my numbers:
Sleep 1sec (try/catch): 188788ms
Sleep 1sec (nullcheck): 207485ms
And the test code:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
public class A
{
private static A _instance;
public static A Instance() {
try {
return _instance.Self();
} catch(NullReferenceException) {
_instance = new A();
}
return _instance.Self();
}
public A Self() {
return this;
}
public void DoSomething()
{
Thread.Sleep(1);
}
}
public class B
{
private static B _instance;
public static B Instance() {
if(_instance == null) {
_instance = new B();
}
return _instance;
}
public void DoSomething()
{
Thread.Sleep(1);
}
}
public class MyClass
{
public static void Main()
{
Stopwatch sw = new Stopwatch();
sw.Reset();
sw.Start();
for(int i = 0; i < 100000; ++i) {
A.Instance().DoSomething();
}
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for(int i = 0; i < 100000; ++i) {
B.Instance().DoSomething();
}
Console.WriteLine(sw.ElapsedMilliseconds);
RL();
}
#region Helper methods
private static void WL(object text, params object[] args)
{
Console.WriteLine(text.ToString(), args);
}
private static void RL()
{
Console.ReadLine();
}
private static void Break()
{
System.Diagnostics.Debugger.Break();
}
#endregion
}
The resulting performance gain is almost 10%, the question is whether it's a micro-op, or it can offer significant performance boost for singleton happy applications (or it's middleware, like, logging)?