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 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 나는 여전히 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에서 불필요한 캐스트로보고됩니다.
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;
both of them have a DoStuff() method defined. For IType it can just return or do custom stuff, whereas ISpecialType can do other stuff.
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.
I did a performance comparsion on two possibilities of type comparison
- myobject.GetType() == typeof(MyClass)
- myobject is MyClass
The result is: Using "is" is about 10x faster !!!
Output:
Time for Type-Comparison: 00:00:00.456
Time for Is-Comparison: 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);
}
}
}
Point Andrew Hare made about performance lost when you perform is
check and then cast was valid but in C# 7.0 we can do is check witch pattern match to avoid additional cast later on:
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 switch
on types:
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.
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:
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
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;
both of them have a DoStuff() method defined. For IType it can just return or do custom stuff, whereas ISpecialType can do other stuff.
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.
참고URL : https://stackoverflow.com/questions/686412/c-sharp-is-operator-performance
'development' 카테고리의 다른 글
Python의 pip를 사용하여 패키지 용 압축 파일을 다운로드하고 보관하는 방법은 무엇입니까? (0) | 2020.09.04 |
---|---|
사람들이 타르볼을 사용하는 이유는 무엇입니까? (0) | 2020.09.04 |
PHP CLI의 새 줄 (0) | 2020.09.04 |
MongoDB가 같지 않음 (0) | 2020.09.04 |
EditText 클릭시 TimePicker 대화 상자 (0) | 2020.09.04 |