委派工作效率
本文关键字:效率 工作 委派 | 更新日期: 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);
}
}