[C#] 장면 뒤에 TypedReference가 왜 있습니까? 너무 빠르고 안전합니다. 거의 마법입니다!

경고 :이 질문은 약간 이단 적입니다 … 종교적 프로그래머들은 항상 좋은 관행을 따르고 있습니다. 🙂

TypedReference 의 사용이 왜 그렇게 암묵적으로 권장 되지 않는지 아는 사람이 있습니까?

일반적인 object포인터 가 아닌 함수를 통해 일반 매개 변수를 전달할 때 ( 값 유형이 필요한 경우 과잉 또는 느리게 사용하는 경우), 불투명 포인터가 필요할 때 등의 용도로 유용합니다. 런타임에 사양을 사용하여 배열의 요소에 빠르게 액세스해야 할 때 (를 사용하여 Array.InternalGetReference). CLR은 이러한 유형의 잘못된 사용을 허용하지 않으므로 왜 권장하지 않습니까? 안전하지 않은 것 같습니다.


내가 찾은 다른 용도 TypedReference:

C #에서 제네릭 “전문화”(유형 안전) :

static void foo<T>(ref T value)
{
    //This is the ONLY way to treat value as int, without boxing/unboxing objects
    if (value is int)
    { __refvalue(__makeref(value), int) = 1; }
    else { value = default(T); }
}

일반 포인터와 함께 작동하는 코드 작성 ( 오용하는 경우 매우 안전하지만 올바르게 사용하면 빠르고 안전합니다) :

//This bypasses the restriction that you can't have a pointer to T,
//letting you write very high-performance generic code.
//It's dangerous if you don't know what you're doing, but very worth if you do.
static T Read<T>(IntPtr address)
{
    var obj = default(T);
    var tr = __makeref(obj);

    //This is equivalent to shooting yourself in the foot
    //but it's the only high-perf solution in some cases
    //it sets the first field of the TypedReference (which is a pointer)
    //to the address you give it, then it dereferences the value.
    //Better be 10000% sure that your type T is unmanaged/blittable...
    unsafe { *(IntPtr*)(&tr) = address; }

    return __refvalue(tr, T);
}

때로는 유용한 메소드 버전의 sizeof명령어 작성 :

static class ArrayOfTwoElements<T> { static readonly Value = new T[2]; }

static uint SizeOf<T>()
{
    unsafe
    {
        TypedReference
            elem1 = __makeref(ArrayOfTwoElements<T>.Value[0] ),
            elem2 = __makeref(ArrayOfTwoElements<T>.Value[1] );
        unsafe
        { return (uint)((byte*)*(IntPtr*)(&elem2) - (byte*)*(IntPtr*)(&elem1)); }
    }
}

복싱을 피하려는 “state”매개 변수를 전달하는 메소드 작성 :

static void call(Action<int, TypedReference> action, TypedReference state)
{
    //Note: I could've said "object" instead of "TypedReference",
    //but if I had, then the user would've had to box any value types
    try
    {
        action(0, state);
    }
    finally { /*Do any cleanup needed*/ }
}

그렇다면 왜 이런 “감지 된”(문서가 부족하여) 사용 되는가? 특별한 안전상의 이유가 있습니까? 포인터와 섞이지 않으면 완벽하게 안전하고 검증 가능한 것처럼 보입니다 (어쨌든 안전하거나 검증 할 수는 없음) …


최신 정보:

실제로 TypedReference두 배나 더 빠를 수있는 샘플 코드 :

using System;
using System.Collections.Generic;
static class Program
{
    static void Set1<T>(T[] a, int i, int v)
    { __refvalue(__makeref(a[i]), int) = v; }

    static void Set2<T>(T[] a, int i, int v)
    { a[i] = (T)(object)v; }

    static void Main(string[] args)
    {
        var root = new List<object>();
        var rand = new Random();
        for (int i = 0; i < 1024; i++)
        { root.Add(new byte[rand.Next(1024 * 64)]); }
        //The above code is to put just a bit of pressure on the GC

        var arr = new int[5];
        int start;
        const int COUNT = 40000000;

        start = Environment.TickCount;
        for (int i = 0; i < COUNT; i++)
        { Set1(arr, 0, i); }
        Console.WriteLine("Using TypedReference:  {0} ticks",
                          Environment.TickCount - start);
        start = Environment.TickCount;
        for (int i = 0; i < COUNT; i++)
        { Set2(arr, 0, i); }
        Console.WriteLine("Using boxing/unboxing: {0} ticks",
                          Environment.TickCount - start);

        //Output Using TypedReference:  156 ticks
        //Output Using boxing/unboxing: 484 ticks
    }
}

(편집 : 게시물의 마지막 버전은 디버그 버전의 코드를 사용했기 때문에 위의 벤치 마크를 편집했으며 [릴리스로 변경하는 것을 잊었습니다] GC에 부담을주지 않습니다.이 버전은 좀 더 현실적이며 내 시스템에서는 TypedReference평균 3 배 이상 빠릅니다 .)



답변

짧은 대답 : 이식성 .

반면 __arglist, __makeref__refvalue되는 언어 확장 , 후드를 구현하는 데 사용되는 구조는와 C # 언어 사양 서류 미 비자 ( vararg호출 규칙, TypedReference유형, arglist, refanytype, mkanyref, 및 refanyval지침)을 완벽에 설명되어 있습니다 CLI 사양 (ECMA-335)가변 인자 라이브러리 .

Vararg 라이브러리에 정의되어 있기 때문에 가변 길이 인수 목록을 지원하는 것이지 그다지 중요하지는 않습니다. 변수 인수 목록은 varargs를 사용하는 외부 C 코드와 인터페이스 할 필요가없는 플랫폼에서 거의 사용되지 않습니다. 이러한 이유로 Varargs 라이브러리는 CLI 프로파일의 일부가 아닙니다. 합법적 CLI 구현은 Varargs 라이브러리가 CLI 커널 프로파일에 포함되지 않으므로 Varargs 라이브러리를 지원하지 않도록 선택할 수 있습니다.

4.1.6 바라 그

가변 인자 기능 세트는 가변 길이 인수 목록 및 런타임 형식의 포인터를 지원합니다.

생략 된 경우 :vararg 호출 규칙 또는 vararg 메소드와 연관된 서명 인코딩 (파티션 II 참조)을 사용하여 메소드를 참조하려고 시도 하면 System.NotImplementedException예외가 발생합니다. CIL 명령어를 사용하는 방법 arglist, refanytype, mkrefany, 및 refanyval던져된다 System.NotImplementedException예외. 예외의 정확한 타이밍은 지정되어 있지 않습니다. 유형을 System.TypedReference정의 할 필요가 없습니다.

업데이트 (댓글로 회신 GetValueDirect) :

FieldInfo.GetValueDirect되어 FieldInfo.SetValueDirect있습니다 하지 기본 클래스 라이브러리의 일부. .NET Framework 클래스 라이브러리와 기본 클래스 라이브러리에는 차이가 있습니다. BCL은 CLI / C #의 적합한 구현에 필요한 유일한 것이며 ECMA TR / 84에 문서화되어 있습니다. 실제로, FieldInfo그 자체는 Reflection 라이브러리의 일부이며 CLI 커널 프로파일에도 포함되어 있지 않습니다.

BCL 외부에서 메소드를 사용하자마자 약간의 이식성을 포기합니다 (Silverlight 및 MonoTouch와 같은 .NET CLI 이외의 구현으로 인해 점점 중요 해지고 있습니다). 구현에서 Microsoft .NET Framework 클래스 라이브러리와의 호환성을 높이고 싶더라도 런타임 에서 특수하게 처리 하지 않고 (기본적으로 성능상의 이점이없는 다른 클래스 GetValueDirect와 동등 하게) 제공 하고 SetValueDirect가져갈 수 있습니다.TypedReferenceTypedReferenceobject

그들이 C #으로 문서화했다면 적어도 몇 가지 의미가 있었을 것입니다.

  1. 다른 기능과 마찬가지로, 새로운 기능의로드 블록 있습니다. 특히 C # 디자인에 적합하지 않으며 런타임시 이상한 구문 확장과 특수한 유형 전달이 필요하기 때문입니다.
  2. C #의 모든 구현은 어떻게 든이 기능을 구현해야하며 CLI에서 전혀 실행되지 않거나 Varargs없이 CLI에서 실행되지 않는 C # 구현에는 반드시 사소하거나 가능하지는 않습니다.

답변

글쎄, 나는 Eric Lippert가 아니기 때문에 Microsoft의 동기에 대해 직접 말할 수는 없지만 추측을해야한다면 TypedReferenceet al. 솔직히 말해서 필요하지 않기 때문에 잘 문서화되어 있지 않습니다.

경우에 따라 성능 저하가 발생하더라도 이러한 기능에 대해 언급 한 모든 사용은 기능없이 수행 할 수 있습니다. 그러나 C # (및 일반적으로 .NET)은 고성능 언어로 설계되지 않았습니다. ( “Java보다 빠름”이 성능 목표라고 생각합니다.)

특정 성능 고려 사항이 제공되지 않았다고 말할 수는 없습니다. 실제로, 포인터와 같은 기능 stackalloc및 특정 최적화 된 프레임 워크 기능은 특정 상황에서 성능을 향상시키기 위해 주로 존재합니다.

타입 안전 의 주요 이점 이있는 제네릭은 또한 TypedReference박싱 및 언 박싱을 피함으로써 성능을 향상시킵니다 . 사실, 왜 당신이 이것을 선호하는지 궁금합니다.

static void call(Action<int, TypedReference> action, TypedReference state){
    action(0, state);
}

이에:

static void call<T>(Action<int, T> action, T state){
    action(0, state);
}

내가 알다시피, 단점은 전자는 JIT가 적고 메모리가 적다는 점이지만 후자는 더 친숙하고 포인터 포인터 참조를 피함으로써 약간 더 빠를 것이라고 가정합니다.

나는 전화 TypedReference및 친구 구현 세부 사항을 호출 합니다. 당신은 그것들에 대한 몇 가지 깔끔한 사용법을 지적했으며, 그것들은 탐구 할 가치가 있다고 생각하지만 구현 세부 사항에 의존하는 일반적인주의 사항이 적용됩니다. 다음 버전은 코드를 손상시킬 수 있습니다.


답변

나는이 질문의 제목은 비꼬는 있어야 여부를 알아낼 수 없습니다 : 된 전통TypedReference‘사실’관리 포인터의 느린, 비 대한, 추악한 사촌이며, 후자의 존재 우리가 무엇을 얻을 C ++ / CLI interior_ptr<T> , 또는 C #의 전통적인 참조 기준 ( ref/ out) 매개 변수 조차도 . 실제로 매번 원래 CLR 배열을 다시 색인화하기 위해 정수를 사용하는 기준 성능에 도달하는 것은 매우 어렵 습니다.TypedReference

슬픈 세부 사항은 여기 있지만 고맙게도 현재는 중요하지 않습니다 …

이 질문은 이제 새에 의해 논쟁을 렌더링 심판 주민 C # 7 ref return 기능에

이러한 새로운 언어 기능은 일류의 탁월한 지원을 제공합니다. 은 신중하게 예측 된 상황에서 진정한 CLR 관리 형 참조 유형 을 선언, 공유 및 조작 할 수 있도록 C # 합니다.

사용 제한은 이전에 요구 된 것보다 엄격하지 않습니다 TypedReference(그리고 성능은 문자 그대로 최악에서 최고로 점프합니다) 그래서에 남아 생각할 유스 케이스 볼) C 번호 를 들어 TypedReference. 예를 들어, 이전을 지속 할 수있는 방법이 없었다 TypedReference에서 GC우수한 관리 포인터의 진정한 같은 존재가 지금은 테이크 아웃되지 않도록, 힙.

그리고 명백하게도, TypedReference거의 완전한 지원 중단 의 소멸은__makeref 정크 힙을 합니다.


답변