【问题标题】:C# Dynamic Keyword — Run-time penalty?C# 动态关键字——运行时惩罚?
【发布时间】:2011-04-16 14:33:45
【问题描述】:

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

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

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

  3. 与 2 相同,但这会带来性能损失(微不足道?可能很重要?)。

【问题讨论】:

标签: c# performance dynamic


【解决方案1】:

我做了一个简单的测试: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 毫秒;在第二个大约 200 毫秒。当然,性能损失取决于您在循环中执行的操作,这些表示可能的最简单操作。

【讨论】:

    【解决方案2】:

    这个问题很混乱。

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

    “定义一个实例”是指“声明一个变量”吗?

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

    “像往常一样进行运行时检查”是什么意思?你想到了什么运行时检查?您是在考虑 IL 验证器 执行的检查,还是考虑由强制类型转换引起的运行时类型检查,还是什么?

    或许最好简单解释一下“动态”的作用。

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

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

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

    回答性能问题的最佳方法是尝试并找出答案 - 如果您需要硬数字,您应该做的就是以两种方式编写代码,使用动态类型和使用已知类型,以及然后拿出秒表并比较时间。只有这样才能知道。

    但是,让我们在抽象级别考虑一些操作对性能的影响。假设你有:

    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 编译器会对其进行 jits。然后,DLR 缓存该编译状态,以便在您询问的 次时,编译器不必重新执行所有这些工作。

    这需要比一纳秒更长的时间。这可能需要数千纳秒。

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

    【讨论】:

    • Eric,如果x,y(假设有+ 运算符)是引用类型怎么办? DLR 的行为是否与您的示例中的行为相同?
    • 埃里克,谢谢。基于this answer,是否可以安全地假设使用Dictionary&lt;Type, dynamic&gt;Dictionary&lt;Type, object&gt; 不会提高性能 - 因为两者都需要对字典值进行装箱和拆箱?用于在字典中存储泛型类型
    • @AdamPlocher:从运行时的角度来看,X&lt;object&gt; 类型和X&lt;dynamic&gt; 类型没有任何区别;到运行时,dynamic 已被删除并替换为object。区别完全在于当您使用字典时编译器将生成的代码;如果字典值类型为objectd[foo].bar() 将是一个错误。如果是dynamic,那么这将是合法的,并且对bar 的调用将在运行时进行分析。
    • @AdamPlocher:如果您担心将 int 值存储在对象字典中的拳击惩罚,您将获得相同的惩罚,将 int 值存储在动态词典中.同样,动态只是戴着滑稽帽子的对象。它是“对象,但将对该表达式的进一步分析推迟到运行时”。
    【解决方案3】:

    据我所知,答案是 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 没有太多经验;这只是我理解它的工作方式。

    【讨论】:

    • 与此问题无关但BindingFlags 用于什么的快速问题?
    • @Braveyard:它基本上是一组标志,用于指定哪些特征适用于某个反射成员。在上面的例子中,相关的特征是它是一个公共实例成员。例如,如果DoLaundry 是一个静态方法,上面的代码将找不到它(您需要添加BindingFlags.Static)。
    • 如果将动态对象作为参数传递给采用非动态参数的函数,尤其是具有多个重载的函数,事情也会变得更加复杂。
    • @Timwi:好电话——老实说,我是在黑暗中拍摄的。回想起来,猜测 JIT 编译器确实没有多大意义。
    【解决方案4】:

    嗯,变量被静态类型化为 dynamic 类型,但据我所知,编译器不会做任何检查。

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

    【讨论】:

      【解决方案5】:

      将变量声明为 dynamic 类似于将其声明为 object。 Dynamic 只是获取另一个标志,表明 成员解析被推迟到运行时

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

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

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

      【讨论】:

      • 有一些显着的区别,例如在声明为对象时只有四种方法可以调用,如果对象的类型发挥了重要作用,我会调用(除非自定义绑定在起作用)在绩效惩罚中
      【解决方案6】:

      据我所知,dynamic 它只绕过编译时检查。类型的解析发生在运行时,就像它对所有类型一样。所以我不认为有任何与之相关的性能损失。

      【讨论】:

      • 两者在运行时相距很远,性能受到影响。例如。使用动态编译器需要进行方法重载解析,以查看调用哪个方法,如果找不到则失败
      猜你喜欢
      • 2012-02-06
      • 1970-01-01
      • 1970-01-01
      • 2018-03-21
      • 1970-01-01
      • 2019-07-10
      • 2015-03-12
      • 2015-03-05
      • 2018-01-27
      相关资源
      最近更新 更多