竹笋

首页 » 问答 » 常识 » 面向接口编程,你考虑过性能吗计算机ja
TUhjnbcbe - 2023/9/18 20:29:00

大家在平时开发中大多都会遵循接口编程,这样就可以方便实现依赖注入也方便实现多态等各种小技巧,但这种是以牺牲性能为代价换取代码的灵活性,万物皆有阴阳,看你的应用场景进行取舍。

一:背景

1.缘由

在项目的性能改造中,发现很多方法签名的返回值都是采用IEnumerable接口,比如下面这段代码:

2.有什么问题

这段代码乍一看也没啥什么性能问题,foreach迭代天经地义,这个还能怎么优化???

1从MSIL中寻找问题

首先我们尽可能把原貌还原出来,简化后的MSIL如下。

从IL中看到了标准的get_Current,MoveNext,Dispose还有一个try,finally,一下子多了这么多方法和关键词,不就是一个简单的foreach迭代数组嘛?至于搞得这么复杂嘛?这样在大数据下怎么快得起来?

还有一个奇葩的事,如果你仔细观察IL代码,比如这句:

[mscorlib]System.Collections.Generic.IEnumerable``1int32::GetEnumerator()

,这个GetEnumerator前面是接口IEnumerable,正常情况下应该是具体迭代类吧,按理说应该会调用Array的GetEnumerator方法,如下所示。

2从windbg中寻找问题

IL中发现的第二个问题我特别好奇,,我们到托管堆上去看下到底是哪一个具体类调用了GetEnumerator()方法。

!clrstack-l!doxx到线程栈上抓list变量

居然有这么一个类型Name:System.SZArrayHelper+SZGenericArrayEnumerator,然来是JIT捣的*,生成了这么一个SZGenericArrayEnumerator类型,接下来把它的方法表达出来看看里面都有啥方法。

可以看到这是一个标准的迭代类,这性能又被拖累了。

二:优化性能

综合上面分析,貌似问题出在了foreach和IEnumerableint这两个方面。

1.IEnumerable

知道了这两点,接下来把代码修改如下:

可以看到上面的IL指令都是非常基础的指令,大多都有CPU指令直接提供支持,非常简洁,大爱~~~

这里有一点要注意:我后来观察foreach不需要改成for,vs编辑器在底层帮我们转换了,看得出来foreach在迭代数组类型的时候还是非常智能的,知道怎么帮助我们优化……修改代码如下:

可以看到上面的IL指令都是非常基础的指令,大多都有CPU指令直接提供支持,非常简洁,大爱

这里有一点要注意:我后来观察foreach不需要改成for,vs编辑器在底层帮我们转换了,看得出来foreach在迭代数组类型的时候还是非常智能的,知道怎么帮助我们优化……修改代码如下:

难以置信的是居然有3-4倍的差距……这就是用灵活性换取性能的代价

好了,本篇就说到这里,希望对你有帮助。

1
查看完整版本: 面向接口编程,你考虑过性能吗计算机ja