빠른 성능이 필요한 프로그램이 있습니다. 내부 루프 중 하나 내에서 개체의 유형을 테스트하여 특정 인터페이스에서 상속되는지 확인해야합니다.
이를 수행하는 한 가지 방법은 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에서 불필요한 캐스트로보고됩니다.
한 가지 아이디어 (당신이 무엇을하고 있는지 모르는 것은 어둠 속에서 약간의 기회입니다),하지만 저는 항상 이와 같은 검사를 피하고 대신 다른 클래스를 갖는 것이 좋습니다. 따라서 몇 가지 검사를 수행하고 유형에 따라 다른 작업을 수행하는 대신 클래스가 자신을 처리하는 방법을 알도록하십시오.
예를 들어 Obj는 ISpecialType 또는 IType 일 수 있습니다.
둘 다 DoStuff () 메서드가 정의되어 있습니다. IType의 경우 반환하거나 사용자 지정 작업을 수행 할 수 있지만 ISpecialType은 다른 작업을 수행 할 수 있습니다.
그러면 캐스팅이 완전히 제거되고 코드가 더 깨끗하고 유지 관리가 쉬워지며 클래스는 자체 작업을 수행하는 방법을 알고 있습니다.
답변
유형 비교의 두 가지 가능성에 대해 성능 비교를 수행했습니다.
- myobject.GetType () == typeof (MyClass)
- 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