我正在学习C#,在我退出编程很长一段时间后(早在90年代,曾经在C和汇编程序中编程),所以我对C#和OOP一般都是新手。
在研究Singleton模式及其实现时,我偶然发现了这种奇怪的行为。我看到有些人使用if(==),而其他人使用if(!=)来检查实例是否已经创建。我想知道两者之间是否存在任何显着的性能差异(从逻辑的角度来看,它们的工作完全相同),经过一些测试后我发现!=比= =快(12%之间)和22%!),我真的不明白为什么。
我知道这个实现不是线程安全的,我写的只是为了试图回答我的好奇心,所以请不要抨击我。 :)
那么,任何人都有答案吗?具体来说,我对发生这种情况感兴趣。这是我使用的代码:
测试代码:
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;
}
}
}
答案 0 :(得分:2)
编译程序时,您可以看到Singleton2有一些额外的指令,但如果它看到实例存在,它会直接分支到最后。这样可以提高你所看到的速度。
以下是两个类的实例方法的IL代码:
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
这是使用MS ILDASM工具(解释型语言反汇编程序)生成的。
答案 1 :(得分:1)
这是我多年来使用过的Singleton基类:
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;
}
答案 2 :(得分:0)
在案例1中,您几乎每次都要执行goto跳过对operator new的调用。在情况2中,如果您尚未初始化变量,则仅执行goto,即一次。简单地说 - 案例2每次通话的工作量较少。