C#是'操作员的performance

我有一个需要快速性能的程序。 在其内部的一个循环中,我需要testing一个对象的types,看它是否从某个接口inheritance。

一种方法是使用CLR的内置types检查function。 最优雅的方法可能是“is”关键字:

if (obj is ISpecialType) 

另一种方法是给基类自己的虚拟GetType()函数返回一个预先定义的枚举值(在我的情况下,实际上,我只需要一个布尔)。 这种方法会很快,但不太优雅。

我听说有一个专门针对“is”关键字的IL指令,但这并不意味着在翻译成本地程序集时执行得更快。 任何人都可以分享一些洞察'是'与另一种方法的performance吗?

更新:感谢所有的知情答案! 似乎有一些有用的观点分布在答案中:Andrew关于'是'自动执行演员的观点是必不可less的,但Binary Worrier和Ian收集的演出数据也非常有用。 如果其中一个答案被编辑,包括所有这些信息,那将是非常好的。

使用is可以伤害的performance,如果,一旦你检查的types,你投的那种types。 实际上将对象转换为您正在检查的types,因此任何后续的转换都是多余的。

如果你打算继续演出,这是一个更好的方法:

 ISpecialType t = obj as ISpecialType; if (t != null) { // use t here } 

我和伊恩在一起,你可能不想这样做。

但是,大家知道,两者之间的差异很小,超过10,000,000次迭代

  • 枚举检查在700毫秒(约)
  • IS检查在1000毫秒(约)

我个人不会这样解决这个问题,但是如果我被迫select一个方法就是内置IS检查,性能差异不值得考虑编码开销。

我的基地和派生类

 class MyBaseClass { public enum ClassTypeEnum { A, B } public ClassTypeEnum ClassType { get; protected set; } } class MyClassA : MyBaseClass { public MyClassA() { ClassType = MyBaseClass.ClassTypeEnum.A; } } class MyClassB : MyBaseClass { public MyClassB() { ClassType = MyBaseClass.ClassTypeEnum.B; } } 

JubJub:根据要求更多的testing信息。

我从一个控制台应用程序(一个debugging版本)运行两个testing,每个testing看起来像下面

 static void IsTest() { DateTime start = DateTime.Now; for (int i = 0; i < 10000000; i++) { MyBaseClass a; if (i % 2 == 0) a = new MyClassA(); else a = new MyClassB(); bool b = a is MyClassB; } DateTime end = DateTime.Now; Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds); } 

在发行版中运行,我得到了60-70毫秒的差距,就像伊恩。

进一步更新 – 2012年10月25日
几年之后,我注意到了一些事情,编译器可以select省略bool b = a is MyClassB ,因为b没有在任何地方使用。

这个代码。 。 。

 public static void IsTest() { long total = 0; var a = new MyClassA(); var b = new MyClassB(); var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 10000000; i++) { MyBaseClass baseRef; if (i % 2 == 0) baseRef = a;//new MyClassA(); else baseRef = b;// new MyClassB(); //bool bo = baseRef is MyClassB; bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B; if (bo) total += 1; } sw.Stop(); Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total); } 

。 。 。 持续显示进入大约57毫秒的检查,枚举比较进入29毫秒。

NB 我仍然更喜欢is检查,差别太小,不在乎

好的,我正在跟某人聊天,决定再试一次。 据我所知,与testing你自己的成员或函数来存储types信息相比, asas的performance都非常好。

我使用了Stopwatch ,我刚学到的可能不是最可靠的方法,所以我也试过UtcNow 。 后来,我也尝试了处理器时间的方法,这似乎与UtcNow类似,包括不可预知的创build时间。 我也尝试使基类不抽象,没有虚拟,但它似乎没有显着的效果。

我用16GB内存的四核Q6600运行这个。 即使有50mil的迭代,数字仍然会反弹大约+/- 50毫秒左右,所以我不会读太多的细微差异。

有趣的是,看到x64创build得更快,但执行速度比x86慢

x64发布模式:
跑表:
如:561ms
是:597ms
基本属性:539ms
基地:555ms
基本RO字段:552ms
虚拟GetEnumType()testing:556ms
虚拟IsB()testing:588ms
创build时间:10416ms

UtcNow:
如:499ms
是:532ms
基本属性:479ms
基地:502ms
基本RO字段:491ms
虚拟GetEnumType():502ms
虚拟布尔IsB():522ms
创build时间:285ms(这个数字对于UtcNow来说似乎不可靠,我也得到了109ms和806ms。)

x86发行模式:
跑表:
如:391ms
是:423ms
基本属性:369ms
基地:321毫秒
基本RO字段:339ms
虚拟GetEnumType()testing:361ms
虚拟IsB()testing:365ms
创build时间:14106ms

UtcNow:
如:348ms
是:375ms
基本属性:329ms
基地:286ms
基本RO字段:309ms
虚拟GetEnumType():321ms
虚拟布尔IsB():332ms
创build时间:544ms(这个数字在UtcNow中似乎不可靠)

以下是大部分代码:

  static readonly int iterations = 50000000; void IsTest() { Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1; MyBaseClass[] bases = new MyBaseClass[iterations]; bool[] results1 = new bool[iterations]; Stopwatch createTime = new Stopwatch(); createTime.Start(); DateTime createStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { if (i % 2 == 0) bases[i] = new MyClassA(); else bases[i] = new MyClassB(); } DateTime createStop = DateTime.UtcNow; createTime.Stop(); Stopwatch isTimer = new Stopwatch(); isTimer.Start(); DateTime isStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { results1[i] = bases[i] is MyClassB; } DateTime isStop = DateTime.UtcNow; isTimer.Stop(); CheckResults(ref results1); Stopwatch asTimer = new Stopwatch(); asTimer.Start(); DateTime asStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { results1[i] = bases[i] as MyClassB != null; } DateTime asStop = DateTime.UtcNow; asTimer.Stop(); CheckResults(ref results1); Stopwatch baseMemberTime = new Stopwatch(); baseMemberTime.Start(); DateTime baseStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B; } DateTime baseStop = DateTime.UtcNow; baseMemberTime.Stop(); CheckResults(ref results1); Stopwatch baseFieldTime = new Stopwatch(); baseFieldTime.Start(); DateTime baseFieldStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B; } DateTime baseFieldStop = DateTime.UtcNow; baseFieldTime.Stop(); CheckResults(ref results1); Stopwatch baseROFieldTime = new Stopwatch(); baseROFieldTime.Start(); DateTime baseROFieldStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B; } DateTime baseROFieldStop = DateTime.UtcNow; baseROFieldTime.Stop(); CheckResults(ref results1); Stopwatch virtMethTime = new Stopwatch(); virtMethTime.Start(); DateTime virtStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B; } DateTime virtStop = DateTime.UtcNow; virtMethTime.Stop(); CheckResults(ref results1); Stopwatch virtMethBoolTime = new Stopwatch(); virtMethBoolTime.Start(); DateTime virtBoolStart = DateTime.UtcNow; for (int i = 0; i < iterations; i++) { results1[i] = bases[i].IsB(); } DateTime virtBoolStop = DateTime.UtcNow; virtMethBoolTime.Stop(); CheckResults(ref results1); asdf.Text += "Stopwatch: " + Environment.NewLine + "As: " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine +"Is: " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base property: " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field: " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field: " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test: " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test: " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time : " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As: " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is: " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property: " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field: " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field: " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType(): " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB(): " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time : " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine; } } abstract class MyBaseClass { public enum ClassTypeEnum { A, B } public ClassTypeEnum ClassType { get; protected set; } public ClassTypeEnum ClassTypeField; public readonly ClassTypeEnum ClassTypeReadonlyField; public abstract ClassTypeEnum GetClassType(); public abstract bool IsB(); protected MyBaseClass(ClassTypeEnum kind) { ClassTypeReadonlyField = kind; } } class MyClassA : MyBaseClass { public override bool IsB() { return false; } public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; } public MyClassA() : base(MyBaseClass.ClassTypeEnum.A) { ClassType = MyBaseClass.ClassTypeEnum.A; ClassTypeField = MyBaseClass.ClassTypeEnum.A; } } class MyClassB : MyBaseClass { public override bool IsB() { return true; } public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; } public MyClassB() : base(MyBaseClass.ClassTypeEnum.B) { ClassType = MyBaseClass.ClassTypeEnum.B; ClassTypeField = MyBaseClass.ClassTypeEnum.B; } } 

安德鲁是正确的。 事实上,在代码分析中,这被Visual Studio报告为不必要的转换。

一个想法(不知道自己在做什么,在黑暗中是一点点),但我一直build议避免这样的检查,而是有另一个class级。 所以,不要根据types做一些检查和不同的操作,让class级知道如何处理自己。

例如,Obj可以是ISpecialType或IType;

他们都定义了一个DoStuff()方法。 对于IType,它可以只返回或做自定义的东西,而ISpecialType可以做其他的东西。

这完全消除了任何投射,使代码更清洁,更容易维护,并且类知道如何执行自己的任务。

我对两种types比较的可能性做了比较

  1. myobject.GetType()== typeof(MyClass)
  2. myobject是MyClass

结果是:使用“是”约快10倍!

输出:

types比较时间:00:00:00.456

是比较时间:00:00:00.042

我的代码:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; namespace ConsoleApplication3 { class MyClass { double foo = 1.23; } class Program { static void Main(string[] args) { MyClass myobj = new MyClass(); int n = 10000000; Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < n; i++) { bool b = myobj.GetType() == typeof(MyClass); } sw.Stop(); Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw)); sw = Stopwatch.StartNew(); for (int i = 0; i < n; i++) { bool b = myobj is MyClass; } sw.Stop(); Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw)); } public static string GetElapsedString(Stopwatch sw) { TimeSpan ts = sw.Elapsed; return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); } } } 

安德鲁·海尔(Andrew Hare)指出,当你执行的is性能丢失检查,然后施放是有效的,但在C#7.0中我们可以做的是检查女巫模式匹配,以避免额外的施放。

 if (obj is ISpecialType st) { //st is in scope here and can be used } 

更进一步,如果您需要检查多种typesC#7.0模式匹配结构现在允许您进行typesswitch

 public static double ComputeAreaModernSwitch(object shape) { switch (shape) { case Square s: return s.Side * s.Side; case Circle c: return c.Radius * c.Radius * Math.PI; case Rectangle r: return r.Height * r.Length; default: throw new ArgumentException( message: "shape is not a recognized shape", paramName: nameof(shape)); } } 

您可以在这里阅读更多关于C#模式匹配的文档。

在任何人想知道的情况下,我已经在Unity引擎2017.1中进行了testing,在带有i5-4200U CPU的笔记本上使用脚本运行时版本.NET4.6(Experimantal)。 结果:

Average Relative To Local Call LocalCall 117.33 1.00 is 241.67 2.06 Enum 139.33 1.19 VCall 294.33 2.51 GetType 276.00 2.35

全文: http : //www.ennoble-studios.com/tuts/unity-c-performance-comparison-is-vs-enum-vs-virtual-call.html

我一直build议避免这样的检查,而是有另一个class级。 所以,不要根据types做一些检查和不同的操作,让class级知道如何处理自己。

例如,Obj可以是ISpecialType或IType;

他们都定义了一个DoStuff()方法。 对于IType,它可以只返回或做自定义的东西,而ISpecialType可以做其他的东西。

这完全消除了任何投射,使代码更清洁,更容易维护,并且类知道如何执行自己的任务。