C # ‘ is’运算符性能

我有一个程序,需要快速性能。在它的一个内部循环中,我需要测试对象的类型,以查看它是否从某个接口继承。

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

if (obj is ISpecialType)

另一种方法是给基类我自己的虚拟 GetType ()函数,它返回一个预定义的枚举值(在我的例子中,实际上,我只需要一个 bool)。这种方法会很快,但不那么优雅。

我听说有一个专门针对“ is”关键字的 IL 指令,但这并不意味着它在被翻译成本地汇编时执行得很快。有人能分享一些关于“是”和其他方法的性能的见解吗?

UPDATE: Thanks for all the informed answers! It seem a couple helpful points are spread out among the answers: Andrew's point about 'is' automatically performing a cast is essential, but the performance data gathered by Binary Worrier and Ian is also extremely useful. It would be great if one of the answers were edited to include 所有 of this information.

59968 次浏览

如果在检查类型之后强制转换为该类型,那么使用 is可能会影响性能。is实际上将对象强制转换为您正在检查的类型,因此任何后续强制转换都是多余的。

如果你无论如何都要选角,这里有一个更好的方法:

ISpecialType t = obj as ISpecialType;


if (t != null)
{
// use t here
}

Andrew 是正确的。事实上,通过代码分析,VisualStudio 会报告这是一个不必要的强制转换。

有一个想法(不知道自己在做什么有点像瞎猜) ,但我总是被建议避免这样检查,而是选择另一门课。因此,与其根据类型执行一些检查和不同的操作,不如让类知道如何处理自己..。

e.g. Obj can be ISpecialType or IType;

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

然后,这将完全删除所有类型转换,使代码更清晰,更易于维护,并且该类知道如何执行自己的任务。

我是 伊恩的,你可能不想这么做。

However, just so you know, there is very little difference between the two, over 10,000,000 iterations

  • 枚举检查的值为 < strong > 700 毫秒(大约)
  • IS 检查在 < strong > 1000进行 毫秒(大约)

我个人不会用这种方法来解决这个问题,但是如果我被迫选择一种方法,那就是内置的 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: 按要求提供更多的测试信息。

我从一个控制台应用程序(一个调试版本)运行了这两个测试,每个测试看起来如下所示

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日
几年之后,我注意到这个问题,编译器可以选择在发行版中省略 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);
}

始终显示 is检查在大约57毫秒进入,枚举比较在29毫秒进入。

NB < em > 我还是喜欢 is检查,差别太小了,不用在意

好吧,所以我正在与某人聊天,并决定测试这个更多。据我所知,与测试自己的成员或函数来存储类型信息相比,asis的性能都非常好。

I used Stopwatch, which I just learned may not be the most reliable approach, so I also tried UtcNow. Later, I also tried Processor time approach which seems similar to UtcNow including unpredictable create times. I also tried making the base class non-abstract with no virtuals but it didn't seem to have a significant effect.

我在 Q6600上运行这个程序,内存为16GB。即使有5000万次迭代,这些数字仍然在 +/-50毫秒左右反弹,所以我不会过多解读这些细微的差异。

有趣的是,x64创建速度更快,但执行速度比 x86慢

x64 Release Mode:
秒表:
作者: 561ms
Is: 597ms
基本属性: 539毫米
基地距离: 555毫米
基地反辐射场: 552米
虚拟 GetEnumType ()测试: 556ms
Virtual IsB ()测试: 588ms
创建时间: 10416ms < br/>

UtcNow:
作者: 499毫秒
是: 532ms
基本属性: 479毫米
基地距离: 502毫米
Base RO field: 491ms
Virtual GetEnumType () : 502ms
Virtual bool IsB () : 522ms
Create Time : 285ms (This number seems unreliable with UtcNow. I also get 109ms and 806ms.)

X86发行模式:
Stopwatch:
作者: 391毫秒
是: 423ms
基本属性: 369毫米
基准面: 321毫米
基地反辐射场: 339毫米
虚拟 GetEnumType ()测试: 361ms
Virtual IsB ()测试: 365ms
创建时间: 14106ms < br/>

UtcNow:
作者: 348ms
是: 375毫秒
基本属性: 329毫米
Base field: 286ms
基地反辐射场: 309毫米
Virtual GetEnumType () : 321ms
Virtual bool IsB () : 332ms
创建时间: 544ms (这个数字在 UtcNow 中似乎不可靠) < br/>

以下是大部分代码:

    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;
}
}

I did a performance comparsion on two possibilities of type comparison

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

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

产出:

类型时间-比较: 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);
}
}
}

我总是被建议避免这样的检查,而是选择另一门课。因此,与其根据类型执行一些检查和不同的操作,不如让类知道如何处理自己..。

例如,Obj 可以是 ISpecialType 或 IType;

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

然后,这将完全删除所有类型转换,使代码更清晰,更易于维护,并且该类知道如何执行自己的任务。

Andrew Hare 关于执行 is检查时性能损失的观点,然后强制转换是有效的,但是在 C # 7.0中,我们可以做的是检查女巫模式匹配,以避免以后额外的强制转换:

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

更进一步,如果你需要检查多个类型 C # 7.0的模式匹配结构现在允许你对类型进行 switch:

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中使用脚本运行时版本进行了测试。使用 i5-4200U CPU 的笔记本电脑上的 NET4.6(实验)。 结果:

相对于本地电话的平均值 本地电话117.331.00 是241.672.06 Enum 139.331.19 VCall 294.332.51 GetType 276.002.35

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