C#动态关键字 - 运行时惩罚?

时间:2010-09-24 05:03:46

标签: c# performance dynamic

在C#中将实例定义为动态意味着:

  1. 编译器不执行编译时类型检查,但运行时检查就像对所有实例一样。

  2. 编译器不执行编译时类型检查,但与其他任何非动态实例不同,会进行运行时检查。

  3. 与2相同,这会带来性能损失(琐碎?可能很重要?)。

6 个答案:

答案 0 :(得分:39)

问题非常混乱。

  

在C#中将实例定义为动态意味着:

通过“定义实例”,您的意思是“声明变量”吗?

  

编译器不执行编译时类型检查,但运行时检查就像它对所有实例一样。

“运行时检查一如既往”是什么意思?您有什么运行时检查?您是在考虑 IL验证程序执行的检查,还是在考虑由强制转换引起的运行时类型检查,或者是什么?

也许最好简单解释“动态”的作用。

首先,动态是从编译器 a 类型的角度出发的。从 CLR 的角度来看,没有 dynamic 这样的东西;在代码实际运行时,所有“动态”实例都已在生成的代码中替换为“object”。

编译器将类型的表达式视为 object 类型的表达式,除了对该表达式的值的所有操作进行分析,编译和执行在运行时基于实例的运行时类型。目标是执行的代码具有相同的语义,就好像编译器在编译时知道运行时类型一样。

您的问题似乎与性能有关。

回答性能问题的最佳方法是尝试并找出 - 如果您需要硬编号,您应该做的是使用动态和使用已知类型双向编写代码,以及然后拿出秒表并比较时间。这是了解的唯一方法。

但是,让我们从抽象层面考虑某些操作的性能影响。假设你有:

int x = 123;
int y = 456;
int z = x + y;

如今,在大多数硬件上添加两个整数大约需要十亿分之一秒。

如果我们让它变得动态会怎样?

dynamic x = 123;
dynamic y = 456;
dynamic z = x + y;

现在在运行时这是做什么的?这个框123和456分为对象,它们在堆上分配内存并进行一些复制。

然后它启动DLR并询问DLR“这个代码站点已经编译过一次,x和y的类型是int和int吗?”

本案的答案是否定的。然后,DLR启动C#编译器的特殊版本,该编译器分析添加表达式,执行重载解析,并吐出表达式树,描述将两个整数相加的lambda。然后,DLR将该lambda编译为动态生成的IL,然后jit编译器将其运行。然后,DLR缓存编译状态,以便您提出第二时间,编译器不必再完成所有工作。

更长而不是纳秒。它可能需要数千纳秒。

这会回答你的问题吗?我真的不明白你在这里问的是什么,但我正在做出最好的猜测。

答案 1 :(得分:7)

据我所知,答案是3。

你可以这样做:

dynamic x = GetMysteriousObject();
x.DoLaundry();

由于编译器没有对x进行类型检查,它将编译此代码,假设您知道自己在做什么。

但这意味着必须进行额外的运行时检查:即检查x的类型,查看它是否有DoLaundry方法不接受任何参数,并执行它。

换句话说,上面的代码是类似就像这样做(我不是说它是相同的,只是绘制一个比较):

object x = GetMysteriousObject();

MethodInfo doLaundry = x.GetType().GetMethod(
    "DoLaundry",
    BindingFlags.Instance | BindingFlags.Public
);

doLaundry.Invoke(x, null);

这绝对不是一件轻而易举的事,但并不是说你的肉眼会发现性能问题。

相信 dynamic的实现涉及到为您完成的一些非常甜蜜的幕后缓存,因此如果您再次运行此代码并x是同一类型,它运行得更快。

但是,不要抱我这么做。我对dynamic没有那么多经验;这只是我理解它的工作方式。

答案 2 :(得分:2)

将变量声明为动态类似于将其声明为对象。动态只是获得另一个标记,表示成员解析延迟到运行时

就性能损失而言 - 它取决于底层对象是什么。那是动态物体的全部意义吗?底层对象可以是Ruby或Python对象,也可以是C#对象。 DLR将在运行时计算出如何解析此对象上的成员调用,此解析方法将确定性能损失。

  

话虽如此 - 肯定会有性能损失。

这就是为什么我们不是简单地开始在整个地方开始使用动态对象。

答案 3 :(得分:2)

好吧,变量是静态类型的类型dynamic但是除此之外,编译器不会进行任何检查。

类型绑定是在运行时完成的,是的,有一个惩罚,但如果dynamic是唯一的选择那么那么。如果您可以使用静态类型解决问题,请执行此操作。话虽这么说,DLR确实调用了站点缓存,这意味着一些开销减少了,因为管道可以在某些情况下重用。

答案 4 :(得分:0)

我做了一个简单的测试:将100000000个赋值作为动态变量与相同数量的直接双精度赋值进行比较,例如

int numberOfIterations = 100000000;

Stopwatch sw = new Stopwatch();
sw.Start();

for (int i = 0; i < numberOfIterations; i++)
{
    var x = (dynamic)2.87; 
}

sw.Stop();
sw.Restart();

for (int i = 0; i < numberOfIterations; i++)
{
    double y = 2.87; 
}
sw.Stop();

在第一个循环(带动态)中,它花费了大约500毫秒;在第二个大约200ms。当然,性能损失取决于您在循环中所做的事情,这些代表可能的最简单操作。

答案 5 :(得分:-1)

至于我不受欢迎dynamic它只绕过编译时间检查。类型的分辨率在运行时发生,就像它对所有类型一样。所以我不认为它有任何性能损失。