动态变量如何影响性能? [英] How does having a dynamic variable affect performance?

查看:32
本文介绍了动态变量如何影响性能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 C# 中 dynamic 的性能有疑问.我读过 dynamic 使编译器再次运行,但它有什么作用?

I have a question about the performance of dynamic in C#. I've read dynamic makes the compiler run again, but what does it do?

是否必须使用 dynamic 变量作为参数重新编译整个方法,或者只是那些具有动态行为/上下文的行?

Does it have to recompile the whole method with the dynamic variable used as a parameter or just those lines with dynamic behavior/context?

我注意到使用 dynamic 变量会使简单的 for 循环速度降低 2 个数量级.

I've noticed that using dynamic variables can slow down a simple for loop by 2 orders of magnitude.

我玩过的代码:

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));
    }

推荐答案

我读过动态使编译器再次运行,但它是做什么的.它是否必须使用用作参数的动态重新编译整个方法,或者更确切地说是那些具有动态行为/上下文的行(?)

I've read dynamic makes the compiler run again, but what it does. Does it have to recompile whole method with the dynamic used as a parameter or rather those lines with dynamic behavior/context(?)

这是交易.

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

For every expression in your program that is of dynamic type, the compiler emits code that generates a single "dynamic call site object" that represents the operation. So, for example, if you have:

class C
{
    void M()
    {
        dynamic d1 = whatever;
        dynamic d2 = d1.Foo();

那么编译器会生成道德上是这样的代码.(实际代码要复杂一些;为了演示目的,这里简化了.)

then the compiler will generate code that is morally like this. (The actual code is quite a bit more complex; this is simplified for presentation purposes.)

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".

See how this works so far? We generate the call site once, no matter how many times you call M. The call site lives forever after you generate it once. The call site is an object that represents "there's going to be a dynamic call to Foo here".

好的,既然您已经有了调用站点,那么调用是如何工作的?

OK, so now that you've got the call site, how does the invocation work?

调用站点是动态语言运行时的一部分.DLR 说嗯,有人试图在这个 here 对象上动态调用方法 foo.我对此有什么了解吗?不.那我最好找出来."

The call site is part of the Dynamic Language Runtime. The DLR says "hmm, someone is attempting to do a dynamic invocation of a method foo on this here object. Do I know anything about that? No. Then I'd better find out."

然后 DLR 会询问 d1 中的对象,看看它是否有什么特别之处.也许它是一个遗留的 COM 对象,或者一个 Iron Python 对象,或者一个 Iron Ruby 对象,或者一个 IE DOM 对象.如果不是其中任何一个,那么它必须是一个普通的 C# 对象.

The DLR then interrogates the object in d1 to see if it is anything special. Maybe it is a legacy COM object, or an Iron Python object, or an Iron Ruby object, or an IE DOM object. If it is not any of those then it must be an ordinary C# object.

这是编译器再次启动的点.不需要词法分析器或解析器,因此 DLR 启动了一个特殊版本的 C# 编译器,它只有元数据分析器、表达式语义分析器和一个发射表达式树而不是 IL 的发射器.

This is the point where the compiler starts up again. There's no need for a lexer or parser, so the DLR starts up a special version of the C# compiler that just has the metadata analyzer, the semantic analyzer for expressions, and an emitter that emits Expression Trees instead of IL.

元数据分析器使用反射来确定 d1 中对象的类型,然后将其传递给语义分析器以询问在方法 Foo 上调用此类对象时会发生什么.重载解析分析器计算出这一点,然后构建一个表达式树——就像你在表达式树 lambda 中调用 Foo 一样——代表那个调用.

The metadata analyzer uses Reflection to determine the type of the object in d1, and then passes that to the semantic analyzer to ask what happens when such an object is invoked on method Foo. The overload resolution analyzer figures that out, and then builds an Expression Tree -- just as if you'd called Foo in an expression tree lambda -- that represents that call.

C# 编译器然后将该表达式树连同缓存策略一起传递回 DLR.策略通常是第二次看到这种类型的对象时,可以重复使用这个表达式树,而不是再次给我回电".然后 DLR 在表达式树上调用 Compile,它调用表达式树到 IL 编译器并在委托中吐出一个动态生成的 IL 块.

The C# compiler then passes that expression tree back to the DLR along with a cache policy. The policy is usually "the second time you see an object of this type, you can re-use this expression tree rather than calling me back again". The DLR then calls Compile on the expression tree, which invokes the expression-tree-to-IL compiler and spits out a block of dynamically-generated IL in a delegate.

然后,DLR 将此委托缓存在与调用站点对象关联的缓存中.

The DLR then caches this delegate in a cache associated with the call site object.

然后它调用委托,并且 Foo 调用发生.

Then it invokes the delegate, and the Foo call happens.

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

The second time you call M, we already have a call site. The DLR interrogates the object again, and if the object is the same type as it was last time, it fetches the delegate out of the cache and invokes it. If the object is of a different type then the cache misses, and the whole process starts over again; we do semantic analysis of the call and store the result in the cache.

这适用于每个涉及动态的表达式.例如,如果您有:

This happens for every expression that involves dynamic. So for example if you have:

int x = d1.Foo() + d2;

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

then there are three dynamic calls sites. One for the dynamic call to Foo, one for the dynamic addition, and one for the dynamic conversion from dynamic to int. Each one has its own runtime analysis and its own cache of analysis results.

有意义吗?

这篇关于动态变量如何影响性能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆