为什么幕后打字?它是如此的快速安全……几乎是神奇的!
-
16-10-2019 - |
题
警告:这个问题有点异端...宗教程序员总是遵守良好的做法,请不要阅读。 :)
有人知道为什么使用 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)); }
}
}
编写一个传递“状态”参数的方法,该参数希望避免拳击:
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
一般。)
解决方案
简短答案: 可移植性.
尽管 __arglist
, __makeref
, , 和 __refvalue
是 语言扩展 并且在C#语言规范中没有记录,用于在引擎盖下实现它们的结构(vararg
召集约定 TypedReference
类型, arglist
, refanytype
, mkanyref
, , 和 refanyval
说明)在 CLI规范(ECMA-335) 在里面 Vararg库.
在Vararg库中定义的,很明显,它们主要是为了支持可变长度参数列表,而不是其他。可变题词列表在不需要与使用varargs的外部C代码接口的平台中几乎没有用。因此,Varargs库不属于任何CLI配置文件。合法的CLI实现可能会选择不支持Varargs库,因为它未包含在CLI内核配置文件中:
4.1.6 vararg
这 Vararg功能集 支持可变长度参数列表和运行时类型的指针。
如果省略: 任何尝试用
vararg
呼叫约定或与Vararg方法相关的签名编码(请参见分区II)应抛出System.NotImplementedException
例外。使用CIL指令的方法arglist
,refanytype
,mkrefany
, , 和refanyval
将扔掉System.NotImplementedException
例外。未指定异常的确切时机。类型System.TypedReference
不必定义。
更新(回复 GetValueDirect
评论):
FieldInfo.GetValueDirect
是 FieldInfo.SetValueDirect
是 不是 基类库的一部分。请注意,.NET Framework类库和Base类库之间存在区别。 BCL是符合CLI/C#实现的唯一需要的内容,并记录在中 ECMA TR/84. 。 (实际上, FieldInfo
本身是反射库的一部分,也不包含在CLI内核配置文件中)。
一旦您使用BCL以外的方法,就可以放弃一些可移植性(随着非-.net CLI实现(如Silverlight和Monotouch)的出现,这变得越来越重要。即使实现想增加与Microsoft .NET框架类库的兼容性,它也可以简单地提供 GetValueDirect
和 SetValueDirect
服用 TypedReference
不制作 TypedReference
特别由运行时间处理(基本上,使它们等同于他们的 object
同行没有绩效益处)。
如果他们在C#中记录了它,它将至少有几个含义:
- 像任何功能一样 可能 成为新功能的障碍,尤其是因为这一功能并不真正适合C#的设计,并且需要怪异的语法扩展和运行时的特殊交接。
- C#的所有实现都必须以某种方式实现此功能,并且对于根本不在CLI之上的C#实现不一定是琐碎的/可能的,或者在没有varargs的CLI之上运行。
其他提示
好吧,我不是埃里克·利珀特 TypedReference
等。没有得到很好的记录,因为坦率地说,您不需要它们。
您提到的这些功能的每种用途都可以在没有它们的情况下完成,尽管在某些情况下会受到惩罚。但是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>
, ,甚至是传统的参考(ref
/out
)参数 C#。实际上,很难制作 TypedReference
甚至达到仅使用整数每次重新索引原始CLR阵列的基线性能。
悲伤的细节是 这里, , 但 值得庆幸的是,现在都不重要...
这些新语言功能在 C# 宣布,共享和操纵真实 CLR
托管参考类型- 在精心预防的情况下进行 - 类型。
使用限制并不比以前需要的更严格 TypedReference
(并且表现实际上是 从最糟糕到最佳),所以我在 C# 为了 TypedReference
. 。例如,以前没有办法坚持 TypedReference
在里面 GC
堆,因此,现在的高级托管指针也是如此。
显然,灭亡 TypedReference
- 至少它几乎完全贬值 - 均值 __makeref
也在扬克普上。