C# でののパフォーマンスについて質問があります。はコンパイラを再度実行させるとdynamic
読みましたが、これは何をするのでしょうか?dynamic
変数をパラメータとして使用してメソッド全体を再コンパイルする必要がありますdynamic
か、それとも動的な動作/コンテキストを持つ行だけを再コンパイルする必要がありますか?
dynamic
変数を使用すると、単純な for ループの速度が 2 桁ほど遅くなる可能性があることに気づきました。
私が試したコード:
internal class Sum2
{
public int intSum;
}
internal class Sum
{
public dynamic DynSum;
public int intSum;
}
class Program
{
private const int ITERATIONS = 1000000;
static void Main(string[] args)
{
var stopwatch = new Stopwatch();
dynamic param = new Object();
DynamicSum(stopwatch);
SumInt(stopwatch);
SumInt(stopwatch, param);
Sum(stopwatch);
DynamicSum(stopwatch);
SumInt(stopwatch);
SumInt(stopwatch, param);
Sum(stopwatch);
Console.ReadKey();
}
private static void Sum(Stopwatch stopwatch)
{
var sum = 0;
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < ITERATIONS; i++)
{
sum += i;
}
stopwatch.Stop();
Console.WriteLine(string.Format("Elapsed {0}", stopwatch.ElapsedMilliseconds));
}
private static void SumInt(Stopwatch stopwatch)
{
var sum = new Sum();
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < ITERATIONS; i++)
{
sum.intSum += i;
}
stopwatch.Stop();
Console.WriteLine(string.Format("Class Sum int Elapsed {0}", stopwatch.ElapsedMilliseconds));
}
private static void SumInt(Stopwatch stopwatch, dynamic param)
{
var sum = new Sum2();
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < ITERATIONS; i++)
{
sum.intSum += i;
}
stopwatch.Stop();
Console.WriteLine(string.Format("Class Sum int Elapsed {0} {1}", stopwatch.ElapsedMilliseconds, param.GetType()));
}
private static void DynamicSum(Stopwatch stopwatch)
{
var sum = new Sum();
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < ITERATIONS; i++)
{
sum.DynSum += i;
}
stopwatch.Stop();
Console.WriteLine(String.Format("Dynamic Sum Elapsed {0}", stopwatch.ElapsedMilliseconds));
}
ベストアンサー1
dynamic はコンパイラを再実行させると読みましたが、これは何をするのでしょうか。パラメーターとして使用される dynamic を使用してメソッド全体を再コンパイルする必要があるのでしょうか、それとも、動的な動作/コンテキストを含む行を再コンパイルする必要があるのでしょうか?
これが取引内容です。
すべてのための表現動的型のプログラムでは、コンパイラは操作を表す単一の「動的呼び出しサイト オブジェクト」を生成するコードを出力します。たとえば、次のようなコードがあるとします。
class C
{
void M()
{
dynamic d1 = whatever;
dynamic d2 = d1.Foo();
すると、コンパイラは次のようなコードを生成します。(実際のコードはこれよりかなり複雑ですが、説明のために簡略化されています。)
class C
{
static DynamicCallSite FooCallSite;
void M()
{
object d1 = whatever;
object d2;
if (FooCallSite == null) FooCallSite = new DynamicCallSite();
d2 = FooCallSite.DoInvocation("Foo", d1);
ここまでの仕組みがわかりましたか?呼び出しサイトを生成します一度、M を何度呼び出しても、呼び出しサイトは一度生成すると永久に存続します。呼び出しサイトは、「ここで Foo への動的な呼び出しが行われる」ことを表すオブジェクトです。
さて、呼び出しサイトができたので、呼び出しはどのように機能するのでしょうか?
呼び出しサイトは、動的言語ランタイムの一部です。DLR は、「うーん、誰かがこのオブジェクトでメソッド foo の動的呼び出しを行おうとしています。これについて何か知っていますか? いいえ。それなら調べたほうがいいでしょう。」と言います。
次に、DLR は d1 内のオブジェクトを調べて、それが何か特別なものであるかどうかを確認します。それは、レガシー COM オブジェクト、Iron Python オブジェクト、Iron Ruby オブジェクト、または IE DOM オブジェクトである可能性があります。それがそれらのいずれでもない場合は、通常の C# オブジェクトである必要があります。
ここでコンパイラが再び起動します。レクサーやパーサーは必要ないので、DLR はメタデータ アナライザー、式のセマンティック アナライザー、IL の代わりに式ツリーを出力するエミッターだけを備えた C# コンパイラの特別なバージョンを起動します。
メタデータ アナライザーは、リフレクションを使用して d1 内のオブジェクトの型を判別し、それをセマンティック アナライザーに渡して、そのようなオブジェクトがメソッド Foo で呼び出されたときに何が起こるかを問い合わせます。オーバーロード解決アナライザーはそれを理解し、式ツリー ラムダで Foo を呼び出した場合と同じように、その呼び出しを表す式ツリーを構築します。
次に、C# コンパイラは、その式ツリーをキャッシュ ポリシーとともに DLR に返します。ポリシーは通常、「このタイプのオブジェクトを 2 回目に参照するときには、再度呼び出すのではなく、この式ツリーを再利用できます」というものです。次に、DLR は式ツリーに対して Compile を呼び出します。これにより、式ツリーから IL へのコンパイラが呼び出され、動的に生成された IL のブロックがデリゲートに出力されます。
次に、DLR はこのデリゲートを、呼び出しサイト オブジェクトに関連付けられたキャッシュにキャッシュします。
次にデリゲートが呼び出され、Foo 呼び出しが行われます。
2 回目に M を呼び出すときには、すでに呼び出しサイトがあります。DLR はオブジェクトを再度調べ、オブジェクトが前回と同じ型である場合は、デリゲートをキャッシュから取得して呼び出します。オブジェクトが異なる型である場合は、キャッシュが失敗し、プロセス全体が最初からやり直されます。呼び出しの意味分析を行い、結果をキャッシュに保存します。
これは、あらゆる表現これには動的要素が含まれます。たとえば、次のような場合です。
int x = d1.Foo() + d2;
それから三つ動的呼び出しサイト。1 つは Foo への動的呼び出し用、1 つは動的追加用、もう 1 つは dynamic から int への動的変換用です。それぞれに独自のランタイム分析と独自の分析結果のキャッシュがあります。
意味をなす?