为什么TypedReference在幕后?它如此快速和安全......几乎是神奇的!

时间:2011-01-21 22:40:19

标签: c# typedreference

警告:这个问题有点异端......宗教程序员总是遵守良好做法,请不要阅读。 :)

有谁知道为什么不鼓励使用TypedReference(隐含地,缺乏文档)?

我发现它有很多用处,比如通过不应该是通用的函数传递泛型参数(当使用object时可能过度或者很慢,如果你需要值类型),当你需要一个不透明的指针时,或者当你需要快速访问一个数组元素时,你在运行时找到它的规范(使用Array.InternalGetReference)。由于CLR甚至不允许错误使用此类型,为什么不鼓励?它似乎不安全或任何东西......


我在TypedReference找到的其他用途:

C#中的“Specializing”泛型(这是类型安全的):

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的速度提高了三倍以上。)

3 个答案:

答案 0 :(得分:41)

简短回答:可移植性

虽然__arglist__makeref__refvalue语言扩展并且在C#语言规范中未记录,但用于在引擎盖下实现它们的构造(vararg调用约定,TypedReference类型,arglistrefanytypemkanyrefrefanyval指令完整记录在{{3在 Vararg库中。

在Vararg库中定义时,很明显它们主要用于支持可变长度的参数列表而不是其他。变量参数列表在不需要与使用varargs的外部C代码接口的平台中几乎没有用处。因此,Varargs库不是任何CLI配置文件的一部分。合法的CLI实现可能选择不支持Varargs库,因为它未包含在CLI内核配置文件中:

  

4.1.6 Vararg

     

vararg功能集支持可变长度参数列表和运行时类型指针。

     

如果省略:任何尝试引用具有vararg调用约定的方法或与vararg方法关联的签名编码(请参阅分区II)都应抛出System.NotImplementedException异常。使用CIL指令arglistrefanytypemkrefanyrefanyval的方法应抛出System.NotImplementedException异常。未指定异常的精确时间。无需定义类型System.TypedReference

更新(回复GetValueDirect评论):

FieldInfo.GetValueDirect FieldInfo.SetValueDirect 是基类库的一部分。请注意,.NET Framework类库和基类库之间存在差异。 BCL是符合CLI / C#实现的唯一要求,并在CLI Specification (ECMA-335)中有记录。 (实际上,FieldInfo本身是Reflection库的一部分,并且不包含在CLI内核配置文件中。)

只要您使用BCL之外的方法,就会放弃一些可移植性(随着Silverlight和MonoTouch等非.NET CLI实现的出现,这变得越来越重要)。即使某个实现想要提高与Microsoft .NET Framework类库的兼容性,它也可以简单地提供GetValueDirectSetValueDirect获取TypedReference而不使TypedReference专门处理运行时(基本上,使它们等同于object对应物而没有性能优势)。

如果他们在C#中记录它,它至少会产生一些影响:

  1. 与任何功能一样,可能成为新功能的障碍,特别是因为这个功能并不真正适合C#的设计,需要奇怪的语法扩展和特殊的类型处理运行。
  2. C#的所有实现都必须以某种方式实现此功能,对于不在CLI之上运行或在没有Varargs的情况下在CLI之上运行的C#实现,它不一定是微不足道/可能的。

答案 1 :(得分:15)

好吧,我不是Eric Lippert,所以我不能直接谈论微软的动机,但如果我冒险猜测,我会说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和朋友的实施细节。你已经指出了它们的一些巧妙用途,我认为它们值得探索,但是依赖于实现细节的常见警告适用 - 下一个版本可能会破坏你的代码。

答案 2 :(得分:3)

我无法弄清楚这个问题的标题是否应该是讽刺的:TypedReference interior_ptr<T>ref是一个缓慢,臃肿,丑陋的堂兄&# 39,真正的&#39;托管指针,后者是我们使用 C ++ / CLI out获得的内容,甚至是中的传统引用(TypedReference / CLR)参数C#即可。 事实上,让TypedReference达到每次只使用整数重新索引原始CLR数组的基线性能非常困难。

可悲的细节是long-established,但 谢天谢地,现在这一切都不重要......

  

C#7 中的新 here ref locals 功能现在提出了这个问题。 >

这些新语言功能在 C#中提供了突出的一流支持,用于声明,共享和操作真正的TypedReference 托管参考类型 < / strong> -types在精心预设的情况下。

使用限制并不比之前TypedReference所要求的更严格(而且性能基本上是ref return),因此我认为 C#中没有任何可以想象的用例为GC。例如,之前没有办法在TypedReference堆中保留__makeref,所以对于优秀的托管指针也是如此,现在不是外卖。

显然,public void Point() // use for calling on a Distance Function function //already in a loop, dont double loop for whatever reason { if(ReaverPos = true) { transform.localPosition = new Vector3(0, 0, 0); Debug.Log (transform.localPosition.y); float ReaverOld = transform.localPosition.y + transform.localPosition.x; Invoke("Shine", 1f); } } public void Shine() //Second call on from Point, Endless loop for Checking Distance based on the Invoke since it last 1 second { float ReaverNew = transform.localPosition.y + transform.localPosition.x; fitDistance = (ReaverNew - ReaverOld); //Debug.Log (fitDistance); } 的消亡 - 或者它至少几乎完全弃用 - 意味着抛出{{1}}在垃圾堆上。