.NET 6 对 StackOverflow 的优化

.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 是表示条件是否取反,truefalse 的反向操作,比如上面的代码,如果 isNegativefalsefilter 表示 >0,则 isNegativetrue , filter 表示为 <=0

第二次看这样的代码的时候,感觉是不是可以优化一下,如果 isNegativetrue 的时候取一个反,于是改成了下面这样

第二版:

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_0func 在调用的时候会调用 <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,然后运行这段代码,输出结果如下:

958d344e700e16fa381cf75329935b7b.png

只有一句 Stack overflow.

然后将项目改成 .net6.0 再运行,输出结果如下:

567e80f78246b3617680acc4600ff6a4.png

可以看到在 .NET 6 下,StackOverflow 的时候会打印出一个重复的次数以及调用的堆栈信息,这对于我们我们排查问题来说会非常的友好,可以让我们更快更准确的找到问题代码,减少焦虑的时间

More

在之前的版本中,如果发生了 StackOverflow 我们需要依赖 Dump 去分析调用堆栈,即使现在微软的文档中还有根据 dump 分析 StackOverflowException 的,这一优化可以帮助我们很好很高效的找到发生错误的代码

References

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值