[c#] C # ‘is’연산자 성능

빠른 성능이 필요한 프로그램이 있습니다. 내부 루프 중 하나 내에서 개체의 유형을 테스트하여 특정 인터페이스에서 상속되는지 확인해야합니다.

이를 수행하는 한 가지 방법은 CLR의 기본 제공 형식 검사 기능을 사용하는 것입니다. 가장 우아한 방법은 아마도 ‘is’키워드 일 것입니다.

if (obj is ISpecialType)

또 다른 접근 방식은 기본 클래스에 미리 정의 된 열거 형 값을 반환하는 내 자신의 가상 GetType () 함수를 제공하는 것입니다 (실제로는 bool 만 필요합니다). 그 방법은 빠르지 만 덜 우아합니다.

특히 ‘is’키워드에 대한 IL 명령어가 있다고 들었지만 이것이 네이티브 어셈블리로 변환 될 때 빠르게 실행된다는 의미는 아닙니다. 누구든지 다른 방법에 비해 ‘is’의 성능에 대한 통찰력을 공유 할 수 있습니까?

업데이트 : 정보에 입각 한 모든 답변에 감사드립니다! 답변에 도움이되는 몇 가지 포인트가 흩어져있는 것 같습니다. 자동으로 캐스트를 수행하는 ‘is’에 대한 Andrew의 포인트는 필수적이지만 Binary Worrier와 Ian이 수집 한 성능 데이터도 매우 유용합니다. 이 모든 정보 를 포함하도록 답변 중 하나를 편집하면 좋을 것 입니다.



답변

is유형을 확인한 후 해당 유형으로 캐스팅 하면 사용하면 성능이 저하 될 수 있습니다. is실제로 검사중인 유형으로 개체를 캐스팅하므로 후속 캐스팅이 중복됩니다.

어쨌든 캐스팅하려는 경우 다음과 같은 더 나은 방법이 있습니다.

ISpecialType t = obj as ISpecialType;

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


답변

Ian 과 함께 있습니다. 아마이 작업을 원하지 않을 것입니다.

그러나 아시다시피, 10,000,000 회가 넘는 두 반복 사이에는 거의 차이가 없습니다.

  • 열거 형 검사는 700
    밀리 초 (대략)에 들어옵니다.
  • IS 검사는 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);
}

릴리스에서 실행하면 Ian처럼 60-70ms의 차이가 발생합니다.

추가 업데이트-2012 년 10 월 25 일
후 나는 이것에 대해 알아 차렸고, 컴파일러는 bool b = a is MyClassBb가 어디에도 사용되지 않기 때문에 릴리스 에서 생략하도록 선택할 수 있습니다 .

이 코드. . .

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 여전히 is수표를 선호합니다 . 차이가 너무 작아서 신경 쓸 수 없습니다.


답변

그래서 누군가와 이것에 대해 이야기하고 있었고 이것을 더 테스트하기로 결정했습니다. 내가 말할 수있는 한, as및 의 성능은 is유형 정보를 저장하기 위해 자체 멤버 또는 함수를 테스트하는 것과 비교할 때 매우 좋습니다.

Stopwatch방금 배운을 사용 했지만 가장 신뢰할 수있는 접근 방식이 아닐 수 있으므로 UtcNow. 나중에 UtcNow예측할 수없는 생성 시간 을 포함 하는 것과 유사한 프로세서 시간 접근 방식도 시도 했습니다. 또한 가상 클래스가없는 기본 클래스를 비 추상적으로 만들려고 시도했지만 큰 효과가없는 것 같습니다.

나는 이것을 16GB RAM의 Quad Q6600에서 실행했습니다. 50mil 반복에도 불구하고 숫자는 여전히 +/- 50 밀리 초 정도 튀어 나오므로 사소한 차이를 너무 많이 읽지 않을 것입니다.

x64가 더 빨리 생성되었지만 x86보다 느리게 실행되는 것을 보는 것은 흥미로 웠습니다.

x64 릴리스 모드 :
스톱워치 :
As : 561ms
Is : 597ms
기본 속성 : 539ms
기본 필드 : 555ms
기본 RO 필드 : 552ms
Virtual GetEnumType () 테스트 : 556ms
Virtual IsB () 테스트 : 588ms
생성 시간 : 10416ms

UtcNow :
As : 499ms
Is : 532ms
기본 속성 : 479ms
기본 필드 : 502ms
Base RO 필드 : 491ms
Virtual GetEnumType () : 502ms
Virtual bool IsB () : 522ms
생성 시간 : 285ms (이 숫자는 UtcNow에서 신뢰할 수없는 것 같습니다. 나도 109ms 및 806ms.)

x86 릴리스 모드 :
스톱워치 :
As : 391ms
Is : 423ms
기본 속성 : 369ms
기본 필드 : 321ms
기본 RO 필드 : 339ms
Virtual GetEnumType () 테스트 : 361ms
Virtual IsB () 테스트 : 365ms
생성 시간 : 14106ms

UtcNow :
As : 348ms
Is : 375ms
기본 속성 : 329ms
기본 필드 : 286ms
기본 RO 필드 : 309ms
Virtual GetEnumType () : 321ms
Virtual bool IsB () : 332ms
생성 시간 : 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에서 불필요한 캐스트로보고됩니다.

한 가지 아이디어 (당신이 무엇을하고 있는지 모르는 것은 어둠 속에서 약간의 기회입니다),하지만 저는 항상 이와 같은 검사를 피하고 대신 다른 클래스를 갖는 것이 좋습니다. 따라서 몇 가지 검사를 수행하고 유형에 따라 다른 작업을 수행하는 대신 클래스가 자신을 처리하는 방법을 알도록하십시오.

예를 들어 Obj는 ISpecialType 또는 IType 일 수 있습니다.

둘 다 DoStuff () 메서드가 정의되어 있습니다. IType의 경우 반환하거나 사용자 지정 작업을 수행 할 수 있지만 ISpecialType은 다른 작업을 수행 할 수 있습니다.

그러면 캐스팅이 완전히 제거되고 코드가 더 깨끗하고 유지 관리가 쉬워지며 클래스는 자체 작업을 수행하는 방법을 알고 있습니다.


답변

유형 비교의 두 가지 가능성에 대해 성능 비교를 수행했습니다.

  1. myobject.GetType () == typeof (MyClass)
  2. myobject는 MyClass입니다.

결과 : “is”를 사용하는 것이 약 10 배 더 빠릅니다 !!!

산출:

유형 비교 시간 : 00 : 00 : 00.456

Is-Comparison 시간 : 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
}

여러 유형 사이를 확인해야하는 경우 더 많은 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 (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