我偶然发现(通过意外),最后一个CLR进行了尾调用优化。我已经使用一段代码进行了测试,但实际上它的行为并不像我预期的那样。我认为尾调用优化可能会在函数中的最后一件事是函数调用时发生。
我正在尝试“破坏”这段代码以防止尾调用优化。
class Program
{
static void Foo(int counter, int limit)
{
try
{
if (counter == limit)
{
return;
}
Foo(++counter, limit);
int d = 1;
d = Bar(d);
//Console.Write(d);
//Thread.Sleep(1);
int baz = 0;
var z = baz + d;
StringBuilder b = new StringBuilder();
b.Append("D");
}
catch (Exception)
{
throw;
}
}
static int Sum(int s)
{
if (s == 1)
{
return s;
}
return s + Sum(s - 1);
}
static int Bar(int d)
{
return d = 10 + d;
}
static void Main(string[] args)
{
int i = 0;
Foo(i, 10000); // jitter
Sum(10000);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Foo(i, 10000);
stopwatch.Stop();
Console.WriteLine(string.Format("time of execution = {0}ms",stopwatch.ElapsedMilliseconds));
stopwatch = new Stopwatch();
stopwatch.Start();
Sum(10000);
stopwatch.Stop();
Console.WriteLine(string.Format("time of execution = {0}ms", stopwatch.ElapsedMilliseconds));
Console.ReadKey();
}
}
但是Foo仍然被优化了。为什么呢?
baz
没有被用于任何有意义的事情,所以我可以看到编译器完全省略它,而且我怀疑 JITter 会使用与d
相同的存储来存储z
,因此每个帧仅为8字节,这远远不足以使默认的1兆栈溢出。不过,最好的方法是查看 IL ;) - Mike Caron/o
进行编译,我没有看到任何真正的尾调用(即tail
后跟call
、calli
或callvirt
)。也许我没有正确地阅读ILDASM... - Mike Caronwhile(true)
循环的原因。 - vcsjones