"as"및 nullable 유형의 성능 놀라움


330

난 그냥 nullable 형식을 다루는 심도 C #의 4 장을 수정하고 "as"연산자를 사용하여 쓸 수있는 섹션을 추가하고 있습니다 :

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    ... // Use x.Value in here
}

나는 이것이 정말로 깔끔하다고 생각했으며 "is"와 캐스트를 사용하여 C # 1에 비해 성능을 향상시킬 수 있다고 생각했다. .

그러나 이것은 사실이 아닙니다. 아래에 샘플 테스트 응용 프로그램이 포함되어 있습니다. 기본적으로 객체 배열 내의 모든 정수를 합산합니다.하지만 배열에는 많은 null 참조와 문자열 참조 및 박스형 정수가 포함됩니다. 벤치 마크는 C # 1에서 사용해야하는 코드, "as"연산자를 사용하는 코드 및 LINQ 솔루션을 시작하는 데 사용됩니다. 놀랍게도 C # 1 코드는이 경우 20 배 더 빠릅니다. 심지어 LINQ 코드 (반복자를 고려할 때 느려질 것으로 예상 됨)도 "as"코드를 능가합니다.

isinstnullable 형식 에 대한 .NET 구현이 정말 느립니까? unbox.any문제를 일으키는 추가 항목 입니까? 이것에 대한 또 다른 설명이 있습니까? 현재 성능에 민감한 상황에서 이것을 사용하지 않는 것에 대한 경고를 포함해야한다고 생각합니다 ...

결과 :

시전 : 10000000 : 121
As : 10000000 : 2211
LINQ : 10000000 : 2143

암호:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i+1] = "";
            values[i+2] = 1;
        }

        FindSumWithCast(values);
        FindSumWithAs(values);
        FindSumWithLinq(values);
    }

    static void FindSumWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int) o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithLinq(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = values.OfType<int>().Sum();
        sw.Stop();
        Console.WriteLine("LINQ: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }
}

8
지트 코드를 보지 않겠습니까? VS 디버거조차도 그것을 보여줄 수 있습니다.
Anton Tykhyy

2
궁금한 점이 있습니다. CLR 4.0으로 테스트 했습니까?
Dirk Vollmar

1
@Anton : 좋은 지적입니다. 언젠가는 할 것입니다 (현재 VS에는 없지만 :) @ divo : 예, 모든 라운드가 더 나쁩니다. 그러나 베타 버전이므로 많은 디버깅 코드가있을 수 있습니다.
Jon Skeet

1
오늘 as은 nullable 유형에 사용할 수 있다는 것을 배웠습니다 . 다른 값 유형에는 사용할 수 없으므로 흥미 롭습니다. 실제로, 더 놀라운.
leppie

3
@Lepp 그것은 가치 유형에서 작동하지 않는 것이 완벽합니다. 그것에 대해 생각 as하고 유형으로 캐스트하려고 시도하면 실패하면 null을 반환합니다. 값 유형을 null로 설정할 수 없습니다
Earlz

답변:


209

분명히 JIT 컴파일러가 첫 번째 경우에 생성 할 수있는 머신 코드가 훨씬 더 효율적입니다. 실제로 도움이되는 한 가지 규칙은 객체를 상자 값과 동일한 유형의 변수로만 상자를 풀 수 있다는 것입니다. 따라서 JIT 컴파일러는 매우 효율적인 코드를 생성 할 수 있으므로 값 변환을 고려할 필요가 없습니다.

것이다 객체가 null는 아니고, 예상되는 형태이며, 소요하지만 몇 기계어 명령 경우 운영자 테스트가 쉽고, 단지 확인된다. 캐스트도 쉽습니다. JIT 컴파일러는 객체에서 값 비트의 위치를 ​​알고이를 직접 사용합니다. 복사 또는 변환이 발생하지 않으며 모든 기계 코드는 인라인이며 약 12 ​​가지 지침이 필요합니다. 이것은 권투가 일반적 일 때 .NET 1.0에서 실제로 효율적이어야했습니다.

int로 캐스팅? 더 많은 작업이 필요합니다. 박스형 정수의 값 표현은의 메모리 레이아웃과 호환되지 않습니다 Nullable<int>. 박스형 열거 형 타입으로 인해 변환이 필요하고 코드가 까다로워집니다. JIT 컴파일러는 JIT_Unbox_Nullable이라는 CLR 헬퍼 함수에 대한 호출을 생성하여 작업을 완료합니다. 이것은 모든 값 유형에 대한 범용 함수이며 유형을 검사하는 많은 코드가 있습니다. 그리고 값이 복사됩니다. 이 코드가 mscorwks.dll에 잠겨 있기 때문에 비용을 추정하기 어렵지만 수백 개의 기계 코드 명령어가있을 수 있습니다.

Linq OfType () 확장 메소드는 is 연산자와 캐스트 도 사용합니다 . 그러나 이것은 일반 유형으로 캐스트됩니다. JIT 컴파일러는 임의의 값 유형으로 캐스트를 수행 할 수있는 JIT_Unbox () 도우미 함수에 대한 호출을 생성합니다. Nullable<int>적은 작업이 필요하다는 점을 감안할 때 캐스트 속도가 느린 이유를 잘 설명하지 못했습니다 . ngen.exe가 여기에 문제를 일으킬 수 있다고 생각합니다.


16
그래, 나는 확신한다. 상속 계층 구조를 올라갈 가능성 때문에 "is"를 잠재적으로 비싸다고 생각하는 데 익숙하다고 생각합니다. 그러나 값 유형의 경우 계층 구조가 불가능하므로 간단한 비트 비교가 가능합니다. . 여전히 nullable 사례의 JIT 코드는 JIT에서보다 훨씬 많이 최적화 될 수 있다고 생각합니다.
Jon Skeet

26

나에게 isinstnullable 유형에서는 실제로 느린 것 같습니다 . 방법에서 FindSumWithCast나는 바꿨다

if (o is int)

if (o is int?)

또한 실행 속도가 크게 느려집니다. 내가 볼 수있는 IL의 유일한 차이점은

isinst     [mscorlib]System.Int32

로 바뀐다

isinst     valuetype [mscorlib]System.Nullable`1<int32>

1
그 이상입니다. "cast"의 경우에는 isinstnull 테스트 후 조건부unbox.any. nullable 경우 무조건이 unbox.any 있습니다.
Jon Skeet

예, 알고 보니 모두 isinstunbox.any느린 nullable 형식에 있습니다.
Dirk Vollmar

@Jon : 캐스트가 필요한 이유에 대한 내 답변을 검토 할 수 있습니다. (이것은 오래되었다는 것을 알고 있지만 방금이 q를 발견하고 CLR에 대해 알고있는 2c를 제공해야한다고 생각했습니다).
Johannes Rudolph

22

이것은 원래 Hans Passant의 훌륭한 답변에 대한 주석으로 시작되었지만 너무 길어서 여기에 약간의 비트를 추가하고 싶습니다.

먼저 C # as연산자는 isinstIL 명령어를 내 보냅니다 ( is오퍼레이터도 마찬가지입니다 ). ( castclass직접 캐스트를 수행하면 컴파일러가 런타임 검사를 생략 할 수 없다는 것을 알고 또 다른 흥미로운 명령이 발생합니다.)

여기에 무엇을 isinst(수행 ECMA 335 파티션 III, 4.6 )

형식 : isinst typeTok

typeTok는 메타 데이터 토큰 (a 인 typeref, typedef또는 typespec원하는 클래스를 나타내는).

경우 typeTok이 nullable이 아닌 값 형식하거나로 해석됩니다 일반적인 매개 변수 유형은 "박스" typeTok을 .

경우 typeTok이 null 허용 유형, Nullable<T>그것은 "박스"로 해석됩니다T

가장 중요한 것은:

실제 유형 (검증되지 추적 방식) 경우 obj가 있다 검증 양도 간 유형 typeTok 다음 isinst성공하고 OBJ (같은 결과를 검증로서의 형 트랙 상태) 그대로 리턴 typeTok를 . 강제 (§1.6) 및 변환 (§3.27)과 달리 isinst개체의 실제 유형을 변경하지 않고 개체 ID를 유지합니다 (파티션 I 참조).

따라서 성능 킬러는 isinst이 경우가 아니라 추가 unbox.any입니다. JIT 코드 만 보았을 때 Hans의 답변에서 명확하지 않았습니다. 일반적으로 C # 컴파일러는 unbox.any이후 를 방출합니다 isinst T?(하지만 참조 유형 isinst T인 경우 수행하는 경우 생략 T).

왜 그렇게합니까? isinst T?명백한 효과가 전혀 없습니다 T?. 대신,이 모든 지시 사항은에 "boxed T"개봉 할 수있는 것이 있다는 것 T?입니다. 실제를 얻으려면 T?, 우리는 여전히 언 박싱해야하는 우리 "boxed T"에게 T?, 컴파일러가 방출 이유입니다 unbox.any후를 isinst. 당신이 그것에 대해 생각하는 경우에 대한 "상자 형식"때문에,이 의미가 T?있습니다 단지 "boxed T"와 제조 castclassisinst언 박스가 일치하지 않아 수행합니다.

Hans의 발견 내용을 표준의 일부 정보로 백업 하면 다음과 같습니다.

(ECMA 335 파티션 III, 4.33) : unbox.any

상자 형식의 값 형식에 적용하면 unbox.any명령은 obj에 포함 된 값을 추출합니다 (type O). (로는 동등 unbox하였다 ldobj.) 기준 입력에인가되면, unbox.any명령과 동일한 효과를 갖는다 castclasstypeTok한다.

(ECMA 335 파티션 III, 4.32) : unbox

일반적으로 unbox상자 개체 내부에 이미 존재하는 값 형식의 주소를 간단히 계산합니다. nullable 값 형식을 개봉 할 때는이 방법을 사용할 수 없습니다. 상자 작업 중에 Nullable<T>값이 boxed로 변환 되기 때문에 Ts구현시 종종 Nullable<T>힙 에서 새로운 것을 제조 하고 새로 할당 된 객체에 대한 주소를 계산해야합니다.


나는 가장 최근에 인용 된 문장에 오타가있을 수 있다고 생각합니다. "... 온 안 ..." "가에있을 실행 스택 ?" 새로운 GC 힙 인스턴스로 다시 언 박싱하면 원래 문제가 거의 동일한 새 문제로 교체됩니다.
Glenn Slayden

19

흥미롭게도, 나는 ( 이 초기 테스트 와 비슷한) dynamic속도가 느려서 운영자 지원에 대한 피드백을 전달했습니다 . 비슷한 이유가 있습니다.Nullable<T>

사랑 해요 Nullable<T>. 또 다른 재미있는 점은 JIT null가 Null을 허용하지 않는 구조체를 발견 하고 제거하더라도 다음과 같이 실패한다는 것입니다 Nullable<T>.

using System;
using System.Diagnostics;
static class Program {
    static void Main() { 
        // JIT
        TestUnrestricted<int>(1,5);
        TestUnrestricted<string>("abc",5);
        TestUnrestricted<int?>(1,5);
        TestNullable<int>(1, 5);

        const int LOOP = 100000000;
        Console.WriteLine(TestUnrestricted<int>(1, LOOP));
        Console.WriteLine(TestUnrestricted<string>("abc", LOOP));
        Console.WriteLine(TestUnrestricted<int?>(1, LOOP));
        Console.WriteLine(TestNullable<int>(1, LOOP));

    }
    static long TestUnrestricted<T>(T x, int loop) {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
    static long TestNullable<T>(T? x, int loop) where T : struct {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
}

Yowser. 정말 고통스러운 차이입니다. Eek.
Jon Skeet

이 모든 것에서 다른 선이 나오지 않았다면 원래 코드 이것에 대한 경고를 포함하게되었습니다 :)
Jon Skeet

나는 이것이 오래된 질문이라는 것을 알고 있지만 " null널을 사용할 수없는 구조체에 대한 JIT 지점 (및 제거)"의 의미를 설명 할 수 있습니까? null런타임 중에 기본값이나 다른 값으로 대체 되는 것을 의미 합니까?
저스틴 모건

2
@Justin-제네릭 메서드는 제네릭 매개 변수 T등을 여러 번 치환하여 런타임에 사용할 수 있습니다 . 스택 등 요구 사항은 인수 (로컬의 스택 공간 등)에 따라 달라 지므로 값 유형과 관련된 고유 순열에 대해 하나의 JIT를 얻습니다. 그러나 참조는 모두 같은 크기이므로 JIT를 공유합니다. 값별 JIT를 수행하는 동안 몇 가지 명백한 시나리오를 확인하고 불가능한 널과 같은 이유로 도달 할 수없는 코드를 소비 하려고 시도 합니다. 완벽하지는 않습니다. 또한 위의 AOT를 무시하고 있습니다.
Marc Gravell

무제한 nullable 테스트는 여전히 2.5 배 느리지 만 count변수를 사용하지 않으면 최적화가 진행됩니다 . 두 경우 모두 Console.Write(count.ToString()+" ");뒤에 추가 watch.Stop();하면 다른 테스트 속도가 약간 느려질 수 있지만 무제한 nullable 테스트는 변경되지 않습니다. null전달 된 케이스를 테스트 할 때 변경 사항도 있으므로, 원래 코드가 실제로 널 검사 및 다른 테스트의 증분을 수행하지 않는지 확인하십시오. Linqpad
마크 허드

12

위의 FindSumWithAsAndHas의 결과입니다. 대체 텍스트

이것은 FindSumWithCast의 결과입니다. 대체 텍스트

결과:

  • 를 사용 as하여 객체가 Int32의 인스턴스인지 먼저 테스트합니다. 후드 아래에서 사용하고 있습니다 isinst Int32(손으로 쓴 코드와 비슷합니다 : if (o is int)). 그리고를 사용 as하면 무조건 객체의 압축을 풉니 다. 그리고 속성을 호출하는 것은 실제 성능을 저하시키는 요소입니다 (여전히 함수입니다), IL_0027

  • 캐스트를 사용하여 object가 int if (o is int); 후드 아래에서 이것을 사용하고 isinst Int32있습니다. int의 인스턴스 인 경우 값 IL_002D를 안전하게 개봉 할 수 있습니다

간단히 말해, 이것은 as접근법 을 사용하는 의사 코드입니다 .

int? x;

(x.HasValue, x.Value) = (o isinst Int32, o unbox Int32)

if (x.HasValue)
    sum += x.Value;    

그리고 이것은 캐스트 접근법을 사용하는 의사 코드입니다.

if (o isinst Int32)
    sum += (o unbox Int32)

따라서 캐스트 ( (int)a[i], 구문은 캐스트처럼 보이지만 실제로는 unboxing, cast 및 unboxing이 동일한 구문을 공유합니다. 다음에는 올바른 용어로 pedantic 할 것입니다) 객체가 결정적으로 int. as접근 방식 을 사용한다고 말할 수는 없습니다 .


11

이 답변을 최신 상태로 유지하려면이 페이지에 대한 대부분의 토론이 이제 C # 7.1.NET 4.7 과 관련 이 있다고 언급 할 가치가 있습니다. 최고의 IL 코드를 생성하는 슬림 구문을 지원하는 을 사용하는 것이 좋습니다.

OP의 원래 예는 ...

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    // ...use x.Value in here
}

간단하게 ...

if (o is int x)
{
    // ...use x in here
}

나는 당신이 .NET을 작성할 때 새로운 구문에 대한 하나 개의 공통 사용은 것으로 나타났습니다 값 유형을 (예 struct에서 C # )이 구현 IEquatable<MyStruct>(대부분의 예상대로). 강력한 형식의 Equals(MyStruct other)메서드를 구현 한 후에 는 다음과 같이 형식화되지 않은 Equals(Object obj)재정의 (에서 상 속됨 Object)를 정상적으로 리디렉션 할 수 있습니다 .

public override bool Equals(Object obj) => obj is MyStruct o && Equals(o);

 


부록은 :Release 빌드 IL의 (각각)이 응답하여 위에서 도시 된 두 예 제 기능을위한 코드는 다음과 같다. 새로운 구문에 대한 IL 코드는 실제로 1 바이트 작지만, 거의 호출하지 않고 (vs. 2) unbox가능한 경우 연산을 완전히 피함으로써 크게 이깁니다 .

// static void test1(Object o, ref int y)
// {
//     int? x = o as int?;
//     if (x.HasValue)
//         y = x.Value;
// }

[0] valuetype [mscorlib]Nullable`1<int32> x
        ldarg.0
        isinst [mscorlib]Nullable`1<int32>
        unbox.any [mscorlib]Nullable`1<int32>
        stloc.0
        ldloca.s x
        call instance bool [mscorlib]Nullable`1<int32>::get_HasValue()
        brfalse.s L_001e
        ldarg.1
        ldloca.s x
        call instance !0 [mscorlib]Nullable`1<int32>::get_Value()
        stind.i4
L_001e: ret

// static void test2(Object o, ref int y)
// {
//     if (o is int x)
//         y = x;
// }

[0] int32 x,
[1] object obj2
        ldarg.0
        stloc.1
        ldloc.1
        isinst int32
        ldnull
        cgt.un
        dup
        brtrue.s L_0011
        ldc.i4.0
        br.s L_0017
L_0011: ldloc.1
        unbox.any int32
L_0017: stloc.0
        brfalse.s L_001d
        ldarg.1
        ldloc.0
        stind.i4
L_001d: ret

이전에 사용 가능한 옵션을 능가하는 새로운 C # 7 구문 의 성능에 대한 내 의견을 입증하는 추가 테스트 는 여기 (특히 'D')를 참조 하십시오 .


9

추가 프로파일 링 :

using System;
using System.Diagnostics;

class Program
{
    const int Size = 30000000;

    static void Main(string[] args)
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "";
            values[i + 2] = 1;
        }

        FindSumWithIsThenCast(values);

        FindSumWithAsThenHasThenValue(values);
        FindSumWithAsThenHasThenCast(values);

        FindSumWithManualAs(values);
        FindSumWithAsThenManualHasThenValue(values);



        Console.ReadLine();
    }

    static void FindSumWithIsThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int)o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += (int)o;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithManualAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            bool hasValue = o is int;
            int x = hasValue ? (int)o : 0;

            if (hasValue)
            {
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Manual As: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenManualHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Manual Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

}

산출:

Is then Cast: 10000000 : 303
As then Has then Value: 10000000 : 3524
As then Has then Cast: 10000000 : 3272
Manual As: 10000000 : 395
As then Manual Has then Value: 10000000 : 3282

이 수치에서 무엇을 추론 할 수 있습니까?

  • 첫째, IS-다음 캐스트 접근 속도가 매우 빠르고보다 같은 접근 방식. 303 대 3524
  • 둘째, .Value는 캐스팅보다 약간 느립니다. 3524 vs 3272
  • 셋째, .HasValue (즉 사용하여 수동을 사용하는 것보다 느린 변두리입니다 입니다 ). 3524 vs 3282
  • 넷째, 사과 - 투 - 사과 비교 (즉, 두 시뮬레이션 HasValue의 지정 및 시뮬레이션 값이 함께 발생 변환) 사이에 일을 같이 시뮬레이션같은 실제 접근 방식을 우리는 볼 수 있습니다 로 시뮬레이션 속도가 매우 빠르고보다 여전히 같은 실제 . 395 대 3524
  • 마지막으로, 첫 번째와 네 번째 결론에 따르면 구현에 문제가 있습니다 ^ _ ^

8

나는 그것을 시도 할 시간이 없지만 다음을 원할 수도 있습니다.

foreach (object o in values)
        {
            int? x = o as int?;

같이

int? x;
foreach (object o in values)
        {
            x = o as int?;

매번 새로운 객체를 생성하는데, 이는 문제를 완전히 설명하지는 않지만 기여할 수 있습니다.


1
아니, 나는 그것을 실행했고 그것은 조금 느리다.
Henk Holterman

2
다른 장소에서 변수를 선언하면 내 경험에서 변수가 캡처 될 때 (실제 의미에 영향을 미치는 시점) 생성 된 코드에만 크게 영향을 미칩니다. 힙을 int?사용하여 스택에 새 인스턴스를 작성하는 것은 아니지만 힙에 새 객체를 작성하지는 않습니다 unbox.any. JIT가 is / cast 사례를 인식하고 한 번만 확인하도록 최적화되어있을 수도 있지만 수제 IL은 두 가지 옵션을 모두 능가 할 수 있습니다.
Jon Skeet

캐스트는 오랫동안 사용되어 왔기 때문에 아마도 캐스트가 최적화 될 것이라고 생각했습니다.
James Black

1
is / cast는 최적화를위한 쉬운 대상입니다. 성가신 일반적인 관용구입니다.
Anton Tykhyy

4
메소드의 스택 프레임이 작성 될 때 로컬 변수가 스택에 할당되므로 메소드에서 변수를 선언하는 경우 아무런 차이가 없습니다. (물론 폐쇄되지 않는 한, 여기에는 해당되지 않습니다.)
Guffa

8

정확한 유형 검사 구문을 시도했습니다.

typeof(int) == item.GetType()item is int버전 만큼 빠른 속도로 수행되며 항상 숫자를 반환합니다 (강조 : Nullable<int>배열에 a 를 쓴 경우에도을 사용해야 함 typeof(int)). null != item여기에 추가 확인이 필요합니다 .

하나

typeof(int?) == item.GetType()(과 대조적으로 item is int?) 빠르게 유지 되지만 항상 false를 반환합니다.

Typeof-construct는 RuntimeTypeHandle을 사용하므로 정확한 유형 확인을 위한 가장 빠른 방법입니다 . 이 경우 정확한 유형은 nullable과 일치하지 않으므로 is/as실제로 Nullable 유형의 인스턴스인지 확인하기 위해 추가적인 노력을 기울여야합니다.

그리고 정직하게 : 당신 is Nullable<xxx> plus HasValue은 무엇을 사나요 ? 아무것도. 항상 기본 (값) 유형 (이 경우)으로 직접 이동할 수 있습니다. 값을 얻거나 "아니오, 요청한 유형의 인스턴스가 아닙니다". (int?)null배열에 쓴 경우에도 유형 검사는 false를 반환합니다.


재미있는 ... 사용의 생각은 "같은"+ HasValue (안 입니다 에만 유형 검사를 수행라는 것이다 플러스 HasValue, 주) 대신 두 배의. 단일 단계에서 "체크 및 언 박스"를 수행하고 있습니다. 그것 같이 느낀다는 해야 빠를 ... 그러나 그것은 명확하지 않다. 마지막 문장의 의미가 확실하지 않지만 상자와 같은 것은 없습니다 int?. int?값 을 상자 에 넣으면 boxed int 또는 null참조 로 끝납니다 .
Jon Skeet 2016 년

7
using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "";
            values[i + 2] = 1;
        }

        FindSumWithCast(values);
        FindSumWithAsAndHas(values);
        FindSumWithAsAndIs(values);


        FindSumWithIsThenAs(values);
        FindSumWithIsThenConvert(values);

        FindSumWithLinq(values);



        Console.ReadLine();
    }

    static void FindSumWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int)o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsAndHas(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Has: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }


    static void FindSumWithAsAndIs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Is: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }







    static void FindSumWithIsThenAs(object[] values)
    {
        // Apple-to-apple comparison with Cast routine above.
        // Using the similar steps in Cast routine above,
        // the AS here cannot be slower than Linq.



        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {

            if (o is int)
            {
                int? x = o as int?;
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then As: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithIsThenConvert(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {            
            if (o is int)
            {
                int x = Convert.ToInt32(o);
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Convert: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }



    static void FindSumWithLinq(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = values.OfType<int>().Sum();
        sw.Stop();
        Console.WriteLine("LINQ: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }
}

출력 :

Cast: 10000000 : 456
As and Has: 10000000 : 2103
As and Is: 10000000 : 2029
Is then As: 10000000 : 1376
Is then Convert: 10000000 : 566
LINQ: 10000000 : 1811

[편집 : 2010-06-19]

참고 : 이전 테스트는 VS2009, VSi7 (회사 개발 시스템)을 사용하여 구성 디버그 VS에서 수행되었습니다.

VS2010을 사용하여 Core 2 Duo를 사용하는 컴퓨터에서 다음이 수행되었습니다.

Inside VS, Configuration: Debug

Cast: 10000000 : 309
As and Has: 10000000 : 3322
As and Is: 10000000 : 3249
Is then As: 10000000 : 1926
Is then Convert: 10000000 : 410
LINQ: 10000000 : 2018




Outside VS, Configuration: Debug

Cast: 10000000 : 303
As and Has: 10000000 : 3314
As and Is: 10000000 : 3230
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 418
LINQ: 10000000 : 1944




Inside VS, Configuration: Release

Cast: 10000000 : 305
As and Has: 10000000 : 3327
As and Is: 10000000 : 3265
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1932




Outside VS, Configuration: Release

Cast: 10000000 : 301
As and Has: 10000000 : 3274
As and Is: 10000000 : 3240
Is then As: 10000000 : 1904
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1936

관심없는 프레임 워크 버전을 사용하고 있습니까? 내 넷북 (.NET 4RC 사용)의 결과는 훨씬 더 극적 입니다. As를 사용하는 버전은 결과보다 훨씬 나쁩니다. .NET 4 RTM을 위해 개선했을까요? 여전히 더 빠를 수 있다고 생각합니다 ...
Jon Skeet

@ Michael : 최적화되지 않은 빌드를 실행 중이거나 디버거에서 실행 중입니까?
Jon Skeet

@ 존 : 디버거에서 최적화되지 않은 빌드
Michael Buen

1
@Michael : 오른쪽 - 나는 :) 크게 관련이없는 같은 디버거에서 성능 결과를보고하는 경향이
존 소총

@ 존 : 디버거에 의해 VS 내부의 의미; 예, 이전 벤치 마크는 디버거에서 수행되었습니다. VS 내부와 외부에서 다시 벤치 마크하고 디버그로 컴파일하고 릴리스로 컴파일했습니다. 편집 확인
Michael Buen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.