C#“是”运算符的性能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/686412/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 13:26:57  来源:igfitidea点击:

C# 'is' operator performance

c#performanceclrgettype

提问by JubJub

I have a program that requires fast performance. Within one of its inner loops, I need to test the type of an object to see whether it inherits from a certain interface.

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

One way to do this would be with the CLR's built-in type-checking functionality. The most elegant method there probably being the 'is' keyword:

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

if (obj is ISpecialType)

Another approach would be to give the base class my own virtual GetType() function which returns a pre-defined enum value (in my case, actually, i only need a bool). That method would be fast, but less elegant.

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

I have heard that there is an IL instruction specifically for the 'is' keyword, but that doesn't mean it executes fast when translated into native assembly. Can anyone share some insight into the performance of 'is' versus the other method?

我听说有一个专门针对“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 allof this information.

更新:感谢所有知情的答案!答案中似乎有几个有用的观点:安德鲁关于“是”自动执行演员的观点是必不可少的,但 Binary Worrier 和 Ian 收集的性能数据也非常有用。如果对其中一个答案进行编辑以包含所有这些信息,那就太好了。

采纳答案by Andrew Hare

Using iscan hurt performance if, once you check the type, you cast to that type. isactually casts the object to the type you are checking so any subsequent casting is redundant.

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

If you are going to cast anyway, here is a better approach:

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

ISpecialType t = obj as ISpecialType;

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

回答by Ian

Andrew is correct. In fact with code analysis this gets reported by Visual Studio as an unnecessary cast.

安德鲁是对的。事实上,通过代码分析,这会被 Visual Studio 报告为不必要的强制转换。

One idea (without knowing what you're doing is a bit of a shot in the dark), but I've always been advised to avoid checking like this, and instead have another class. So rather than doing some checks and having different actions depending on the type, make the class know how to process itself...

一个想法(不知道你在做什么是在黑暗中的一个镜头),但我一直被建议避免这样检查,而是有另一堂课。因此,与其进行一些检查并根据类型进行不同的操作,不如让类知道如何处理自己......

e.g. Obj can be ISpecialType or IType;

例如,Obj 可以是 ISpecialType 或 IType;

both of them have a DoStuff() method defined. For IType it can just return or do custom stuff, whereas ISpecialType can do other stuff.

它们都定义了 DoStuff() 方法。对于 IType,它可以只返回或执行自定义操作,而 ISpecialType 可以执行其他操作。

This then completely removes any casting, makes the code cleaner and easier to maintain, and the class knows how to do it's own tasks.

然后,这完全消除了任何强制转换,使代码更清晰、更易于维护,并且类知道如何完成它自己的任务。

回答by Binary Worrier

I'm with Ian, you probably don't want to do this.

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

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

但是,正如您所知,两者之间的差异很小,超过 10,000,000 次迭代

  • The enum check comes in at 700milliseconds (approx)
  • The IS check comes in at 1000milliseconds (approx)
  • 枚举检查发生在700毫秒(大约)
  • IS 检查发生在 1000毫秒(大约)

I personally wouldn't fix this problem this way, but if I was forced to pick one method it would be the built in IS check, the performance difference isn't worth considering the coding overhead.

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

My base and derived classes

我的基类和派生类

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: As requested more info on the tests.

JubJub:根据要求提供有关测试的更多信息。

I ran both tests from a console app (a debug build) each test looks like the following

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

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

Running in release, I get a difference of 60 - 70 ms, like Ian.

在发布中运行,我得到了 60 - 70 毫秒的差异,就像 Ian。

Further Update - Oct 25th 2012
After a couple of years away I noticed something about this, the compiler can choose to omit bool b = a is MyClassBin release because b isn't used anywhere.

进一步更新 - 2012 年 10 月 25 日
几年之后,我注意到了一些关于这一点的事情,编译器可以选择bool b = a is MyClassB在发布中省略,因为 b 没有在任何地方使用。

This code . . .

这段代码。. .

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

. . . consistently shows the ischeck coming in at approx 57 milliseconds, and the enum comparison coming in at 29 milliseconds.

. . . 一致地显示is检查在大约 57 毫秒处进入,枚举比较在 29 毫秒处进入。

NBI'd still prefer the ischeck, the difference is too small to care about

NB我还是比较喜欢ischeck,差别太小,不用在意

回答by Jared Thirsk

Ok so I was chatting about this with someone and decided to test this more. As far as I can tell, the performance of asand isare both very good, compared to testing your own member or function to store type information.

好的,所以我正在和某人聊天并决定对此进行更多测试。据我所知,与测试您自己的成员或函数来存储类型信息相比,as和的性能is都非常好。

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 UtcNowincluding 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.

我用过Stopwatch,我刚学到的可能不是最可靠的方法,所以我也尝试了UtcNow. 后来,我还尝试了处理器时间方法,这似乎类似于UtcNow包含不可预测的创建时间。我还尝试使基类非抽象,没有虚拟,但它似乎没有显着影响。

I ran this on a Quad Q6600 with 16GB RAM. Even with 50mil iterations, the numbers still bounce around +/- 50 or so millisec so I wouldn't read too much into the minor differences.

我在配备 16GB RAM 的 Quad Q6600 上运行此程序。即使进行了 5000 万次迭代,数字仍会在 +/- 50 毫秒左右反弹,因此我不会过多地了解细微差别。

It was interesting to see that x64 created faster but executed as/is slower than x86

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

x64 Release Mode:
Stopwatch:
As: 561ms
Is: 597ms
Base property: 539ms
Base field: 555ms
Base RO field: 552ms
Virtual GetEnumType() test: 556ms
Virtual IsB() test: 588ms
Create Time : 10416ms

x64 发布模式:
秒表:
As:561ms
是:597ms
基本属性:539ms
基本字段:555ms
基本 RO 字段:552ms
Virtual GetEnumType() 测试:556ms
Virtual IsB() 测试:588ms
创建时间:10416ms

UtcNow:
As: 499ms
Is: 532ms
Base property: 479ms
Base field: 502ms
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.)

UtcNow:
As: 499ms
Is: 532ms
Base property: 479ms
Base field: 502ms
Base RO field: 491ms
Virtual GetEnumType(): 502ms
Virtual bool IsB(): 522ms
Create Time : 285ms (这个数字对于 UtcNow 似乎不可靠。我也得到了和 806 毫秒。)

x86 Release Mode:
Stopwatch:
As: 391ms
Is: 423ms
Base property: 369ms
Base field: 321ms
Base RO field: 339ms
Virtual GetEnumType() test: 361ms
Virtual IsB() test: 365ms
Create Time : 14106ms

x86 发布模式:
秒表:
As:391ms
是:423ms
基本属性:369ms
基本字段:321ms
基本 RO 字段:339ms
Virtual GetEnumType() 测试:361ms
Virtual IsB() 测试:365ms
创建时间:14106ms

UtcNow:
As: 348ms
Is: 375ms
Base property: 329ms
Base field: 286ms
Base RO field: 309ms
Virtual GetEnumType(): 321ms
Virtual bool IsB(): 332ms
Create Time : 544ms (This number seems unreliable with UtcNow.)

UtcNow:
As: 348ms
Is: 375ms
Base property: 329ms
Base field: 286ms
Base RO field: 309ms
Virtual GetEnumType(): 321ms
Virtual bool IsB(): 332ms
Create Time : 544ms (这个数字对于 UtcNow 似乎不可靠。)

Here's most of the code:

这是大部分代码:

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

回答by Knasterbax

I did a performance comparsion on two possibilities of type comparison

我对类型比较的两种可能性进行了性能比较

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

The result is: Using "is" is about 10x faster !!!

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

Output:

输出:

Time for Type-Comparison: 00:00:00.456

类型比较时间:00:00:00.456

Time for Is-Comparison: 00:00:00.042

Is-比较时间:00:00:00.042

My Code:

我的代码:

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

回答by user3802787

I've always been advised to avoid checking like this, and instead have another class. So rather than doing some checks and having different actions depending on the type, make the class know how to process itself...

我一直被建议避免像这样检查,而是有另一堂课。因此,与其进行一些检查并根据类型进行不同的操作,不如让类知道如何处理自己......

e.g. Obj can be ISpecialType or IType;

例如,Obj 可以是 ISpecialType 或 IType;

both of them have a DoStuff() method defined. For IType it can just return or do custom stuff, whereas ISpecialType can do other stuff.

它们都定义了 DoStuff() 方法。对于 IType,它可以只返回或执行自定义操作,而 ISpecialType 可以执行其他操作。

This then completely removes any casting, makes the code cleaner and easier to maintain, and the class knows how to do it's own tasks.

然后,这完全消除了任何强制转换,使代码更清晰、更易于维护,并且类知道如何完成它自己的任务。

回答by Krzysztof Branicki

Point Andrew Hare made about performance lost when you perform ischeck and then cast was valid but in C# 7.0 we can do is check witch pattern match to avoid additional cast later on:

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

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

Further more if you need to check between multiple types C# 7.0 pattern matching constructs now allow you to do switchon types:

此外,如果您需要在多种类型之间进行检查,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));
    }
}

You can read more about pattern matching in C# in documentation here.

您可以在此处的文档中阅读有关 C# 模式匹配的更多信息。

回答by Gru

In case anyone is wondering, I've made tests in Unity engine 2017.1, with scripting runtime version .NET4.6(Experimantal) on a notebook with i5-4200U CPU. Results:

如果有人想知道,我已经在 Unity 引擎 2017.1 中进行了测试,在带有 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

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

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

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