장면 뒤에 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*/ }
}
그렇다면 왜 이런 "감지 된 (discouraged)"사용 (문서 부족)입니까? 특별한 안전상의 이유가 있습니까? 포인터와 섞이지 않으면 완벽하게 안전하고 검증 가능한 것처럼 보입니다 (어쨌든 안전하거나 검증 할 수 없음) ...
최신 정보:
실제로 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
}
}
(Edit: I edited the benchmark above, since the last version of the post used a debug version of the code [I forgot to change it to release], and put no pressure on the GC. This version is a bit more realistic, and on my system, it's more than three times faster with TypedReference
on average.)
Short answer: portability.
While __arglist
, __makeref
, and __refvalue
are language extensions and are undocumented in the C# Language Specification, the constructs used to implement them under the hood (vararg
calling convention, TypedReference
type, arglist
, refanytype
, mkanyref
, and refanyval
instructions) are perfectly documented in the CLI Specification (ECMA-335) in the Vararg library.
Being defined in the Vararg Library makes it quite clear that they are primarily meant to support variable-length argument lists and not much else. Variable-argument lists have little use in platforms that don't need to interface with external C code that uses varargs. For this reason, the Varargs library is not part of any CLI profile. Legitimate CLI implementations may choose not to support Varargs library as it's not included in the CLI Kernel profile:
4.1.6 Vararg
The vararg feature set supports variable-length argument lists and runtime-typed pointers.
If omitted: Any attempt to reference a method with the
vararg
calling convention or the signature encodings associated with vararg methods (see Partition II) shall throw theSystem.NotImplementedException
exception. Methods using the CIL instructionsarglist
,refanytype
,mkrefany
, andrefanyval
shall throw theSystem.NotImplementedException
exception. The precise timing of the exception is not specified. The typeSystem.TypedReference
need not be defined.
Update (reply to GetValueDirect
comment):
FieldInfo.GetValueDirect
are FieldInfo.SetValueDirect
are not part of Base Class Library. Note that there's a difference between .NET Framework Class Library and Base Class Library. BCL is the only thing required for a conforming implementation of the CLI/C# and is documented in ECMA TR/84. (In fact, FieldInfo
itself is part of the Reflection library and that's not included in CLI Kernel profile either).
As soon as you use a method outside BCL, you are giving up a bit of portability (and this is becoming increasingly important with the advent of non-.NET CLI implementations like Silverlight and MonoTouch). Even if an implementation wanted to increase compatiblility with the Microsoft .NET Framework Class Library, it could simply provide GetValueDirect
and SetValueDirect
taking a TypedReference
without making the TypedReference
specially handled by the runtime (basically, making them equivalent to their object
counterparts without the performance benefit).
Had they documented it in C#, it would have had at least a couple implications:
- Like any feature, it may become a roadblock to new features, especially since this one doesn't really fit in the design of C# and requires weird syntax extensions and special handing of a type by the runtime.
- All implementations of C# have to somehow implement this feature and it's not necessarily trivial/possible for C# implementations that don't run on top of a CLI at all or run on top of a CLI without Varargs.
Well, I'm no Eric Lippert, so I can't speak directly of Microsoft's motivations, but if I were to venture a guess, I'd say that TypedReference
et al. aren't well documented because, frankly, you don't need them.
Every use you mentioned for these features can be accomplished without them, albeit at a performance penalty in some cases. But C# (and .NET in general) isn't designed to be a high-performance language. (I'm guessing that "faster than Java" was the performance goal.)
That's not to say that certain performance considerations haven't been afforded. Indeed, such features as pointers, stackalloc
, and certain optimized framework functions exist largely to boost performance in certain situations.
Generics, which I'd say have the primary benefit of type safety, also improve performance similarly to TypedReference
by avoiding boxing and unboxing. In fact, I was wondering why you'd prefer this:
static void call(Action<int, TypedReference> action, TypedReference state){
action(0, state);
}
to this:
static void call<T>(Action<int, T> action, T state){
action(0, state);
}
The trade-offs, as I see them, are that the former requires fewer JITs (and, it follows, less memory), while the latter is more familiar and, I would assume, slightly faster (by avoiding pointer dereferencing).
I'd call TypedReference
and friends implementation details. You've pointed out some neat uses for them, and I think they're worth exploring, but the usual caveat of relying on implementation details applies—the next version may break your code.
I can't figure out whether this question's title is supposed to be sarcastic: It has been long-established that TypedReference
is the slow, bloated, ugly cousin of 'true' managed pointers, the latter being what we get with C++/CLI interior_ptr<T>
, or even traditional by-reference (ref
/out
) parameters in C#. In fact, it's pretty hard to make TypedReference
even reach the baseline performance of just using an integer to re-index off the original CLR array every time.
The sad details are here, but thankfully, none of this matters now...
This question is now rendered moot by the new ref locals and ref return features in C# 7
These new language features provide prominent, first-class support in C# for declaring, sharing, and manipulating true CLR
managed reference type-types in carefully prescibed situations.
The use restrictions are no stricter than what was previously required for TypedReference
(and the performance is literally jumping from worst to best), so I see no remaining conceivable use case in C# for TypedReference
. For example, previously there was no way to persist a TypedReference
in the GC
heap, so the same being true of the superior managed pointers now is not a take-away.
And obviously, the demise of TypedReference
—or its nearly complete deprecation at least—means throw __makeref
on the junkheap as well.
'IT story' 카테고리의 다른 글
TextView-프로그래밍 방식으로 텍스트 크기를 설정해도 작동하지 않는 것 같습니다 (0) | 2020.07.08 |
---|---|
문자열에서 확장을 제거하는 방법 (실제 확장 만!) (0) | 2020.07.08 |
Dagger 2에서 구성 요소 (오브젝트 그래프)의 수명주기를 결정하는 요소는 무엇입니까? (0) | 2020.07.08 |
동일한 벡터에서 요소를 푸시 백하는 것이 안전합니까? (0) | 2020.07.08 |
Eclipse Workspaces : 무엇을 위해 왜? (0) | 2020.07.08 |