舞台裏でタイプレファレンスがあるのはなぜですか?それはとても速くて安全です...ほとんど魔法です!

StackOverflow https://stackoverflow.com/questions/4764573

  •  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 2倍の速さ(またはそれ以上)にすることができます:

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に圧力をかけません。このバージョンはもう少し現実的で、私のシステムでは、3倍以上速くなります TypedReference 平均して。)

役に立ちましたか?

解決

短い答え: 移植性.

その間 __arglist, __makeref, 、 と __refvalue それは 言語拡張機能 C#言語仕様では文書化されていません。これは、フードの下でそれらを実装するために使用されています(vararg 電話会議、 TypedReference タイプ、 arglist, refanytype, mkanyref, 、 と refanyval 指示)は完全に文書化されています CLI仕様(ECMA-335) の中に Vararg Library.

Vararg Libraryで定義されると、それらが主に変動する長さの引数リストをサポートすることを目的としていることが明らかになります。変数argumentリストは、Varargsを使用する外部Cコードとインターフェイスする必要のないプラットフォームではほとんど使用されていません。このため、VarargsライブラリはCLIプロファイルの一部ではありません。正当なCLI実装は、CLIカーネルプロファイルに含まれていないため、Varargsライブラリをサポートしないことを選択する場合があります。

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フレームワーククラスライブラリとベースクラスライブラリには違いがあることに注意してください。 BCLは、CLI/C#の適合実装に必要な唯一のものであり、で文書化されています ECMA TR/84. 。 (実際には、 FieldInfo それ自体は反射ライブラリの一部であり、CLIカーネルプロファイルにも含まれていません)。

BCL以外のメソッドを使用するとすぐに、少しの移植性を放棄します(そして、これはSilverlightやMonotouchなどの非.NET CLI実装の出現によりますます重要になっています)。実装がMicrosoft .NET Frameworkクラスライブラリとの互換性を高めたいと考えていたとしても、単純に提供することができます GetValueDirectSetValueDirect 取る TypedReference 作らずに TypedReference ランタイムによって特別に処理されます(基本的に、それらに相当するものにします object パフォーマンスのメリットのないカウンターパート)。

彼らがC#でそれを文書化したなら、それは少なくともいくつかの意味を持っていたでしょう:

  1. 他の機能と同様に、それ 五月 特にこれがC#の設計に実際に収まらず、奇妙な構文拡張機能とランタイムまでのタイプの特別なハンドリングが必要なため、新しい機能への障害になります。
  2. C#のすべての実装は、この機能を何らかの形で実装する必要があり、CLIの上に実行されないC#実装や、VarargsのないCLIの上で実行されないC#実装では必ずしも些細な/可能ではありません。

他のヒント

まあ、私はエリック・リパートではないので、マイクロソフトの動機について直接話すことはできませんが、推測をするなら、私はそれが言うと言う TypedReference et 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>, 、または伝統的な副参照(ref/out)パラメーター C#。実際、作るのはかなり難しいです TypedReference 整数を使用して、毎回元のCLRアレイから再インデックスするだけのベースラインパフォーマンスに到達することもできます。

悲しい詳細はそうです ここ, 、 しかし ありがたいことに、これは今の問題ではありません...

この質問は現在、新しいものによって論争されています 地元の人を参照してくださいRef Return の機能 C#7

これらの新しい言語機能は、顕著な一流のサポートを提供します C# 真の宣言、共有、操作のため CLR 管理された参照タイプ- 慎重に予測された状況のタイプ。

使用制限は、以前に必要だったものより厳しくありません TypedReference (そして、パフォーマンスは文字通りです 最悪から最高のジャンプ)、だから私は考えられるままの使用ケースが残っていないと私は見ています C# にとって TypedReference. 。たとえば、以前は持続する方法はありませんでした TypedReference の中に GC ヒープ、そのため、優れた管理されたポインターについても同じことが当てはまります。現在は持ち帰りではありません。

そして明らかに、の終mise TypedReference- または、少なくともほぼ完全な非推奨 - 平均が投げられます __makeref Junkheapでも。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top