假设我写了这样一个类(函数的数量并不重要,但实际上,将会有大约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,我宁愿不考虑在不同的地方实施十次相同的扩展功能。
因此,可以多次执行该操作(即,该类的初始方法可以由代表“包裹”两次或甚至三次)。我想知道性能开销会是什么样的。
谢谢!
答案 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计算时,我得到以下结果:
因此虽然存在差异,但它并不是很大,而且可能微不足道。
现在,我的计算中可能存在错误,因此我在下面添加了整个代码。我在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);
}
}