Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/310.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#单例实现性能_C#_Performance_Singleton - Fatal编程技术网

C#单例实现性能

C#单例实现性能,c#,performance,singleton,C#,Performance,Singleton,我在学习C#的过程中,在我退出编程很长一段时间后(早在90年代初,我就习惯于用C和汇编语言编程),所以我对C#和OOP基本上是相当陌生的 在研究Singleton模式及其实现时,我偶然发现了这种奇怪的行为。我看到一些人使用if(=),而其他人使用if(!=)来检查实例是否已经创建。我想知道这两者之间是否有显著的性能差异(从逻辑角度看,它们的ofc工作原理完全相同),经过一些测试,我发现比==(12%到22%!)快得多,我真的不明白为什么 我知道这个实现不是线程安全的,顺便说一句,我写它只是为了满

我在学习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不,我们确实不希望该类具有公共无参数构造函数。这就是它使用反射的原因。