具有动态变量如何影响性能?

我对dynamicC#的性能有疑问。我读过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));

}

回答:

我读过动态使编译器再次运行,但它能做什么。它是否必须使用动态参数作为参数重新编译整个方法,还是使用动态行为/上下文(?)

这是交易。

对于程序中动态类型的每个 表达式 ,编译器都会发出代码,该代码生成代表操作的单个“动态调用站点对象”。因此,例如,如果您有:

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说:“嗯,有人试图对此here对象进行方法foo的动态调用。对此我是否一无所知?不,那么我最好找出来。”

然后DLR询问d1中的对象以查看它是否特殊。可能是旧版COM对象,Iron Python对象,Iron Ruby对象或IE

DOM对象。如果不是这些,则它必须是普通的C#对象。

这是编译器再次启动的点。不需要词法分析器或解析器,因此DLR启动了C#编译器的特殊版本,该版本仅具有元数据分析器,表达式的语义分析器以及发出表达式树而不是IL的发射器。

元数据分析器使用反射来确定d1中对象的类型,然后将其传递给语义分析器以询问在方法Foo上调用该对象时会发生什么。重载解析器会找出这个问题,然后构建一个表达式树-

就像您在表达式树lambda中调用Foo一样,代表该调用。

然后,C#编译器将该表达式树与缓存策略一起传递回DLR。该策略通常是“第二次看到这种类型的对象时,您可以重新使用此表达式树,而无需再次给我回电”。然后DLR在表达式树上调用Compile,后者会调用表达式树到IL的编译器,并在委托中吐出一块动态生成的IL。

然后,DLR将该委托缓存在与呼叫站点对象关联的缓存中。

然后,它调用委托,然后进行Foo调用。

第二次您致电M,我们已经有一个呼叫站点。DLR再次询问该对象,如果该对象与上次的类型相同,则它将委托从缓存中取出并调用它。如果对象的类型不同,则高速缓存将丢失,并且整个过程将重新开始;否则,整个过程将重新开始。我们对调用进行语义分析,并将结果存储在缓存中。

对于涉及动态的 发生这种情况。因此,例如,如果您有:

int x = d1.Foo() + d2;

那么就有 三个

动态呼叫站点。一种用于对Foo的动态调用,一种用于动态加法,一种用于从dynamic到int的动态转换。每个人都有自己的运行时分析和分析结果缓存。

合理?

以上是 具有动态变量如何影响性能? 的全部内容, 来源链接: utcz.com/qa/398351.html

回到顶部