委托堆栈效率

时间:2011-04-26 12:00:25

标签: c# performance random delegates inline

假设我写了这样一个类(函数的数量并不重要,但实际上,将会有大约3或4个。)

    private class ReallyWeird
    {
        int y;

        Func<double, double> f1;
        Func<double, double> f2;
        Func<double, double> f3;

        public ReallyWeird()
        {
            this.y = 10;

            this.f1 = (x => 25 * x + y);
            this.f2 = (x => f1(x) + y * f1(x));
            this.f3 = (x => Math.Log(f2(x) + f1(x)));
        }

        public double CalculusMaster(double x)
        {
            return f3(x) + f2(x);
        }
    }

我想知道C#编译器是否可以优化这样的代码,以便它不会经历多次堆栈调用。

是否能够在编译时内联代理?如果是,在哪些条件和哪些限制?如果不是,为什么会有答案?

另一个问题,可能更重要的是:它会比我声明f1, f2 and f3作为方法慢得多吗?

我问这个因为我想尽可能保持我的代码为DRY,所以我想实现一个扩展基本随机数生成器(RNG)功能的静态类:它的方法接受一个委托(例如来自方法{{1 RNG)并返回另一个NextInt()委托(例如用于生成Func s),建立在前者之上。只要有许多不同的RNG可以生成ulong s,我宁愿不考虑在不同的地方实施十次相同的扩展功能。

因此,可以多次执行该操作(即,该类的初始方法可以由代表“包裹”两次或甚至三次)。我想知道性能开销会是什么样的。

谢谢!

3 个答案:

答案 0 :(得分:2)

如果您使用Expression Trees而不是完整的Func&lt;&gt;编译器将能够优化表达式。

编辑为了澄清,请注意我并不是说运行时会优化表达式树本身(它不应该),而是因为生成的Expression<>树是{{ 1}} d在一个步骤中,JIT引擎将只看到重复的子表达式,并能够优化,合并,替换,快捷方式以及它通常做的其他事情。

(我不确定它是否适用于所有平台,但至少它应该能够充分利用JIT引擎)


评论回复

  • 首先,表达树具有的潜力应等于执行速度,如Func&lt;&gt; (但是Func&lt;&gt;将不具有相同的运行时成本 - JITting可能在jitting封闭范围时发生;在ngen的情况下,它甚至将是AOT,而不是表达式树)

  • 第二:我同意Expression Trees很难使用。请参阅此处了解如何撰写表达式的famous simple example。然而,更复杂的例子很难得到。如果我有时间,我会看看是否可以提出一个PoC,看看MS.Net和MONO在这些情况下在MSIL中实际产生了什么。

  • 第三:不要忘记Henk Holterman可能是正确的说这是过早的优化(尽管提前编写.Compile()而不是Expression<>只是增加了灵活性)< / p>

  • 最后,当你真的考虑到这么做的时候,你可能会考虑使用Compiler As A Service(Mono已经有了,我相信微软仍然会推出它?)。

答案 1 :(得分:2)

我不希望编译器对此进行优化。复杂性(因为代表)将是巨大的。

我也不担心这里有几个堆栈帧。使用25 * x + y堆栈+调用开销可能很大,但调用其他一些方法(PRNG),而你在这里关注的部分变得非常边缘。

答案 2 :(得分:2)

我编译了一个快速测试应用程序,我将委托方法与我将每个计算定义为函数的方法进行了比较。

当为每个版本进行10.000.000计算时,我得到以下结果:

  • 使用代理运行:平均920毫秒
  • 使用常规方法调用运行:平均730毫秒

因此虽然存在差异,但它并不是很大,而且可能微不足道。

现在,我的计算中可能存在错误,因此我在下面添加了整个代码。我在Visual Studio 2010中以发布模式编译它:

class Program
{
    const int num = 10000000;

    static void Main(string[] args)
    {

        for (int run = 1; run <= 5; run++)
        {
            Console.WriteLine("Run " + run);
            RunTest1();
            RunTest2();
        }
        Console.ReadLine();
    }

    static void RunTest1()
    {

        Console.WriteLine("Test1");

        var t = new Test1();

        var sw = Stopwatch.StartNew();
        double x = 0;
        for (var i = 0; i < num; i++)
        {
            t.CalculusMaster(x);
            x += 1.0;
        }
        sw.Stop();

        Console.WriteLine("Total time for " + num + " iterations: " + sw.ElapsedMilliseconds + " ms");

    }

    static void RunTest2()
    {

        Console.WriteLine("Test2");

        var t = new Test2();

        var sw = Stopwatch.StartNew();
        double x = 0;
        for (var i = 0; i < num; i++)
        {
            t.CalculusMaster(x);
            x += 1.0;
        }
        sw.Stop();

        Console.WriteLine("Total time for " + num + " iterations: " + sw.ElapsedMilliseconds + " ms");

    }
}

class Test1 
{
    int y;

    Func<double, double> f1;
    Func<double, double> f2;
    Func<double, double> f3;

    public Test1()
    {
        this.y = 10;

        this.f1 = (x => 25 * x + y);
        this.f2 = (x => f1(x) + y * f1(x));
        this.f3 = (x => Math.Log(f2(x) + f1(x)));
    }

    public double CalculusMaster(double x)
    {
        return f3(x) + f2(x);
    }

}
class Test2
{
    int y;


    public Test2()
    {
        this.y = 10;
    }

    private double f1(double x)
    {
        return 25 * x + y;
    }

    private double f2(double x)
    {
        return f1(x) + y * f1(x);
    }

    private double f3(double x)
    {
        return Math.Log(f2(x) + f1(x));
    }

    public double CalculusMaster(double x)
    {
        return f3(x) + f2(x);
    }

}