舞台裏でタイプレファレンスがあるのはなぜですか?それはとても速くて安全です...ほとんど魔法です!
-
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クラスライブラリとの互換性を高めたいと考えていたとしても、単純に提供することができます GetValueDirect
と SetValueDirect
取る TypedReference
作らずに TypedReference
ランタイムによって特別に処理されます(基本的に、それらに相当するものにします object
パフォーマンスのメリットのないカウンターパート)。
彼らがC#でそれを文書化したなら、それは少なくともいくつかの意味を持っていたでしょう:
- 他の機能と同様に、それ 五月 特にこれがC#の設計に実際に収まらず、奇妙な構文拡張機能とランタイムまでのタイプの特別なハンドリングが必要なため、新しい機能への障害になります。
- 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でも。