委派工作效率

本文关键字:效率 工作 委派 | 更新日期: 2023-09-27 17:52:54

假设我已经编写了这样一个类(函数的数量并不重要,但实际上,大约有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)功能的静态类:它的方法接受一个委托(例如来自RNG的方法NextInt())并返回另一个Func委托(例如用于生成ulong s),建立在前者之上。只要有许多不同的RNG可以生成int,我就不愿意考虑在不同的地方执行所有相同的扩展功能。

因此,这个操作可能被执行多次(即类的初始方法可能被委托"包装"两次甚至三次)。我想知道性能开销会是怎样的。

谢谢!

委派工作效率

如果您使用表达式树而不是完整的Func<>编译器将能够优化表达式。

Edit澄清一下,请注意,我并不是说运行时将优化表达式树本身(它不应该),而是说,由于结果Expression<>树是.Compile() d在一个步骤中,JIT引擎将简单地看到重复的子表达式,并能够优化,合并,替换,快捷方式和它通常做的其他事情。

(我不确定它在所有平台上都能做到,但至少它应该能够充分利用JIT引擎)


<

评论反应/strong>

  • 首先,表达式树可能具有与Func<>相同的执行速度(但是Func<>不会具有相同的运行时成本- jit可能在jit封闭作用域时发生;在ngen的情况下,它甚至是AOT,而不是表达式树)

  • 第二:我同意表达式树很难使用。这里有一个关于如何编写表达式的著名的简单示例。然而,更复杂的例子很难找到。如果我有时间,我会看看我是否能想出一个PoC,看看MS.Net和MONO实际上在MSIL中为这些情况生成了什么。

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

  • 最后,当你真的想在这方面走得更远的时候,你可能会考虑使用Compiler As A Service (Mono已经有了,我相信微软还会有这样的服务)。

我不希望编译器优化这一点。(由于代表的关系)将会非常复杂。

我也不会担心这里的一些堆栈框架。对于25 * x + y,堆栈+调用开销可能非常大,但是调用一些其他方法(PRNG),您在这里关注的部分变得非常次要。

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

在对每个版本进行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);
    }
}