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

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

问题描述

我有一个动态的C#中的性能问题。我读过的动态,使再次运行编译器,但它有什么作用?

它必须重新编译以用作参数或仅那些与动态行为/上下文行的动态变量整的方法?

我注意到,使用动态变量可以通过2个数量级放慢一个简单的for循环。

code我已经打了:

 内部类SUM2
{
    公众诠释intSum;
}内部类总和
{
    公共动态DynSum;
    公众诠释intSum;
}类节目
{
    私人const int的ITERATIONS = 1000000;    静态无效的主要(字串[] args)
    {
        VAR秒表=新的秒表();
        动态参数=新的对​​象();
        DynamicSum(秒表);
        SumInt(秒表);
        SumInt(秒表,参数);
        总和(秒表);        DynamicSum(秒表);
        SumInt(秒表);
        SumInt(秒表,参数);
        总和(秒表);        Console.ReadKey();    }    私有静态无效总和(秒表)
    {
        变种总和= 0;
        stopwatch.Reset();
        stopwatch.Start();
        的for(int i = 0; I< ITERATIONS;我++)
        {
            总和+ =我;
        }
        stopwatch.Stop();        Console.WriteLine(的String.Format(经历{0},stopwatch.ElapsedMilliseconds));
    }    私有静态无效SumInt(秒表)
    {
        VAR总和=新的SUM();
        stopwatch.Reset();
        stopwatch.Start();
        的for(int i = 0; I< ITERATIONS;我++)
        {
            sum.intSum + =我;
        }
        stopwatch.Stop();        Console.WriteLine(的String.Format(类点心诠释消逝{0},stopwatch.ElapsedMilliseconds));
    }    私有静态无效SumInt(秒表,动态参数)
    {
        VAR总和=新SUM2();
        stopwatch.Reset();
        stopwatch.Start();
        的for(int i = 0; I< ITERATIONS;我++)
        {
            sum.intSum + =我;
        }
        stopwatch.Stop();        Console.WriteLine(的String.Format(类点心诠释消逝{0} {1},stopwatch.ElapsedMilliseconds,param.GetType()));
    }    私有静态无效DynamicSum(秒表)
    {
        VAR总和=新的SUM();
        stopwatch.Reset();
        stopwatch.Start();
        的for(int i = 0; I< ITERATIONS;我++)
        {
            sum.DynSum + =我;
        }
        stopwatch.Stop();        Console.WriteLine(的String.Format(动态总和已用{0},stopwatch.ElapsedMilliseconds));
    }


解决方案

  

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


这里的交易。​​

对于每一个的前pression 的在你的程序,它是动态类型,编译器code,产生一个动态调用站点对象即重新presents的操作。因此,举例来说,如果有:

  C类
{
    无效M()
    {
        动态D1 =什么;
        动态D2 = d1.Foo();

那么编译器将产生code,它在道德上是这样的。 (实际code是相当多的复杂的;这是简化了presentation目的)

  C类
{
    静态DynamicCallSite FooCallSite;
    无效M()
    {
        对象D1 =什么;
        反对D2;
        如果(FooCallSite == NULL)FooCallSite =新DynamicCallSite();
        D2 = FooCallSite.DoInvocation(富,D1);

请参阅如何工作这么远?我们生成调用点的一次的,不管你有多少次调用M.调用点住你生成一次后,直到永远。呼叫站点是重新presents对象那里将是富这里动态调用。

好了,现在你已经有了调用点,请问调用的工作?

调用点是动态语言运行时的一部分。 DLR的说:嗯,有人试图做一个foo方法的动态调用此位置的对象。我是否知道呢?否。然后我最好还是了解一下。

在DLR然后询问对象D1,看它是什么特别的事情。也许这是一个传统的COM对象,或铁Python对象,或铁Ruby对象,或者一个IE浏览器的DOM对象。如果不是任何一样东西,它必须是一个普通的C#对象。

这就是编译器重新启动点。没有必要为一个词法分析器或解析器,所以DLR启动C#编译器,仅仅有元数据分析,语义分析仪前pressions,和发射前pression树木,而不是发射器的特殊版本伊尔。

元数据分析器使用反射来确定D1的对象的类型,然后传递该至语义分析器问当这样的对象上的方法的Foo调用会发生什么。重载决策分析数字说出来,然后建立一个Ex pression树 - 就好像你在一个前pression树拉姆达称为富 - 那些重新presents调用

C#编译器然后通过该前pression树回到DLR与高速缓存策略一起。该政策通常是你看到这个类型的对象第二次,你可以重新使用前pression树,而不是叫我回来。然后,DLR的前pression树,它调用前pression树到IL编译器和吐出动态生成的IL块在委托调用编译。

在DLR然后缓存此代表在与呼叫站点对象关联的缓存。

然后调用委托,和富调用发生。

您拨打M上的第二次,我们已经有了一个调用点。 DLR的再次询问的对象,如果该对象是同一类型的,因为它是最后一次,它提取委托从缓存中,并调用它。如果对象是一个不同类型则高速缓存未命中,并且整个过程重新开始过的;我们做呼叫语义分析,并将结果存储在缓存中。

这里面有每前pression 涉及的动态。因此,例如,如果您有:

  INT X = d1.Foo()+ D2;

然后还有的的动态调用站点。一为富来,一个是动态调用的动态此外,还有一个从动态的动态转换为int。每个人都有自己的运行时分析和自己的分析结果的缓存。

请有意义吗?

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?

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

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

Code I have played with:

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(?)

Here's the deal.

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

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?

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

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.

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.

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.

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.

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

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

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;

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.

Make sense?

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

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