.NET 6 对 StackOverflow 的优化
Intro
去年写了一系列的傻逼代码, 其中有一篇 写了多年代码,你会 StackOverflow 吗,昨天一不小心又写了一个 StackOverflow 代码。。然后想把新的代码加到原来 StackOverflow 的示例中,把原来的示例项目改成了 .NET 6 项目,偶然间发现 .NET 6 对于 StackOverlow 有一些小优化,且看下文
Sample
首先来看我们的 StackOverflow 的示例代码,最初的版本是没有 stack overflow 的,后来想着优化一下哎就改成了 StackOverflow 的版本了。。
下面示例中有两个参数,可以理解为方法参数,会是动态的参数,不是一成不变的
var isNegative = false;
var oper = '>';
第一个版本的代码是这样的:
Func<int, bool> filter;
if (!isNegative)
{
filter = oper == '>'
? (i => i > 0)
: (i => i < 0);
}
else
{
filter = oper == '>'
? (i => i <= 0)
: (i => i >= 0);
}
大概意图是这样的,isNegative
是表示条件是否取反,true
是 false
的反向操作,比如上面的代码,如果 isNegative
为 false
,filter
表示 >0
,则 isNegative
为 true
, filter
表示为 <=0
第二次看这样的代码的时候,感觉是不是可以优化一下,如果 isNegative
为 true
的时候取一个反,于是改成了下面这样
第二版:
filter = oper == '>'
? (i => i < 0)
: (i => i > 0);
if (isNegative)
{
filter = i => !filter(i);
}
这样看起来是不是简单一些,但就是这样的代码会导致 StackOverflow
上面的代码 filter = i => !filter(i);
等同于下面这样的代码:
Func<int, bool> filter = x => x>0;
filter = delegate (int i) =>
{
return filter(i);
};
如果觉得没有什么问题,我们可以再往下看,将上面的代码使用 ILSpy 使用 C# 1.0 翻译一下,可以看到翻译后的结果如下:
<>c__DisplayClass4_0 <>c__DisplayClass4_ = new <>c__DisplayClass4_0();
<>c__DisplayClass4_.func = <>c.<>9__4_0 ?? (<>c.<>9__4_0 = new Func<int, bool>(<>c.<>9.<Main>b__4_0));
<>c__DisplayClass4_.func = new Func<int, bool>(<>c__DisplayClass4_.<Main>b__1);
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
public static Func<int, bool> <>9__4_0;
internal bool <Main>b__4_0(int x)
{
return x > 0;
}
}
private sealed class <>c__DisplayClass4_0
{
public Func<int, bool> func;
internal bool <Main>b__1(int x)
{
return func(x);
}
}
从上面的代码可以看出来,这里发生了一个死循环,<>c__DisplayClass4_0
的 func
在调用的时候会调用 <Main>b__1
方法,而这个方法会再次调用 func
这个委托,之后互相调用起来最后就爆栈了...
那么上面的代码是不是可以优化呢,可能也谈不上优化,只是用了模式匹配换了一种写法罢了,第三版写法如下:
Func<int, bool> filter = (isNegative, oper) switch
{
(false, '>') => i => i > 0,
(false, _) => i => i < 0,
(true, '>') => i => i <= 0,
(true, _) => i => i >= 0,
};
这里使用了 switch
的模式匹配来简化代码,效果和第一种方式完全一样,只是换了一种写法
StackOverflow enhancement
我们拿前面第二种写法的一个最简化的代码来做一个测试,代码如下:
Func<int, bool> filter = x => x>0;
filter = delegate (int i) =>
{
return filter(i);
};
Console.WriteLine(filter(10));
首先把项目修改成 .netcoreapp3.1
,然后运行这段代码,输出结果如下:
只有一句 Stack overflow.
然后将项目改成 .net6.0
再运行,输出结果如下:
可以看到在 .NET 6 下,StackOverflow 的时候会打印出一个重复的次数以及调用的堆栈信息,这对于我们我们排查问题来说会非常的友好,可以让我们更快更准确的找到问题代码,减少焦虑的时间
More
在之前的版本中,如果发生了 StackOverflow 我们需要依赖 Dump 去分析调用堆栈,即使现在微软的文档中还有根据 dump 分析 StackOverflowException 的,这一优化可以帮助我们很好很高效的找到发生错误的代码
References
https://github.com/WeihanLi/SamplesInPractice/blob/master/StupidSamples/StackOverflowSample.cs#L49