C#单例实现性能
我在学习C#的过程中,在我退出编程很长一段时间后(早在90年代初,我就习惯于用C和汇编语言编程),所以我对C#和OOP基本上是相当陌生的 在研究Singleton模式及其实现时,我偶然发现了这种奇怪的行为。我看到一些人使用if(=),而其他人使用if(!=)来检查实例是否已经创建。我想知道这两者之间是否有显著的性能差异(从逻辑角度看,它们的ofc工作原理完全相同),经过一些测试,我发现比==(12%到22%!)快得多,我真的不明白为什么 我知道这个实现不是线程安全的,顺便说一句,我写它只是为了满足我的好奇心,所以请不要责怪我。:) 那么,有人对此有答案吗?具体来说,我对为什么会发生这种情况很感兴趣。这是我使用的代码: 测试代码:C#单例实现性能,c#,performance,singleton,C#,Performance,Singleton,我在学习C#的过程中,在我退出编程很长一段时间后(早在90年代初,我就习惯于用C和汇编语言编程),所以我对C#和OOP基本上是相当陌生的 在研究Singleton模式及其实现时,我偶然发现了这种奇怪的行为。我看到一些人使用if(=),而其他人使用if(!=)来检查实例是否已经创建。我想知道这两者之间是否有显著的性能差异(从逻辑角度看,它们的ofc工作原理完全相同),经过一些测试,我发现比==(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;
}
}
}
}
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;
}
}
}
if:
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;
}
}
}
}
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;
}
}
}
使用系统;
使用系统诊断;
名称空间单体速度测试
{
公共密封类单音1
{
私有静态Singleton1实例=null;
私有单例1(){}
公共静态Singleton1实例()
{
如果(instance==null)instance=new Singleton1();
返回实例;
}
公共静态长测试单例(UInt64周期)
{
秒表sw=Stopwatch.StartNew();
对于(UInt64 i=0;i
Singleton2类与!=在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;
}
}
}
使用系统;
使用系统诊断;
名称空间单体速度测试
{
公共密封类单音2
{
私有静态Singleton2实例=null;
私有单例2(){}
公共静态Singleton2实例()
{
如果(instance!=null)返回实例;
return instance=new Singleton2();
}
公共静态长测试单例(UInt64周期)
{
秒表sw=Stopwatch.StartNew();
对于(UInt64 i=0;i
在案例1中,您几乎每次都在进行转到,以跳过对新操作员的呼叫。在案例2中,只有在没有初始化变量的情况下才执行goto,即一次。简单地说,案例2每次调用的工作量更少。这是我多年来使用的单例基类:
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;
}
公共类SingletonBase,其中T:class
{
静态单碱基()
{
}
公共静态只读T实例=
typeof(T).InvokeMember(typeof(T).Name,
BindingFlags.CreateInstance|
BindingFlags.Instance|
BindingFlags.Public|
BindingFlags.NonPublic,
null,null,null)作为T;
}
编译程序时,您可以看到Singleton2有一些额外的指令,但如果它看到实例存在,它会直接分支到末尾。这给了它你所看到的速度提升
以下是两个类的实例方法的IL代码:
单音1:
.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
单音2:
.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工具(解释语言反汇编程序)生成的。不用担心微观优化,测试有多广泛?我还没有亲自测试代码,但垃圾收集器可能会影响您的结果。将Singleton2切换为在Singleton1之前运行,您会发现现在Singleton2可能需要更长的时间。将最常被点击的案例放在顶部可以避免跳转指令,并且更容易在CPU上运行。这不是微观优化;这是纳米优化,事实上,我在实时系统中最关键的性能代码中达到了这一级别,但由于竞争,这些系统的性能几乎会直接转化为我公司的奖金支票。不要听@EhsanSajjad的话,7%是真的。@hoodaticus一个小时的7%的操作可能是一笔巨大的交易。花费.0000000 1秒的操作的7%非常非常重要。大多数程序员不会编写这样的优化在他们的一生中都很重要的程序,而那些这样做的程序员只会在非常罕见的情况下这样做。我怀疑这可能是编译器编写汇编代码的方式,并且==if中使用的JMP可能会影响性能。我想这种影响只有在处理对象时才明显,而不是在处理原语时。我错了吗?一个引用是一个原始的GALANDILL。OOOP你是对的,我总是倾向于忘记它,并考虑基本的原始类型只有int,浮点,布尔等:我将尝试用谷歌搜索所有的IL指令,以便更好地理解代码,但第一个使用的IL代码的第一眼基本上是这样的,当单例不为null时,有一个分支比第二个分支(000b处的分支)多,所以我猜(我在汇编时使用的iirc)在这种情况下,由于IP寄存器中新地址的额外加载而导致性能下降。您是否应该在那里添加
new()
约束?@hoodaticus不,我们确实不希望该类具有公共无参数构造函数。这就是它使用反射的原因。