加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 建站 > 正文

.NET 性能优化的技巧

发布时间:2019-08-22 07:15:45 所属栏目:建站 来源:gejigeji
导读:最大化内联 内联是将方法体(method body)复制到调用站点的技术,这样我们就可以避免跳转、参数传递和寄存器保存/恢复等繁琐过程。除了节省这些之外,内联还是实现其他优化的必要条件。 不不过Roslyn(C#的编译器)没有内联代码,它是通过JIT实现的,大多数优

尽可能不要通过将值类型转换为引用类型来装箱值类型。这是常见的建议,但在API设计中需要考虑一些因素。在Hagar中,可以接受值类型的接口和方法定义是通用的,以便它们可以专门用于精确类型并避免装箱/拆箱成本。结果,没有热路径拳击。在某些情况下仍然存在拳击,例如异常方法的字符串格式。可以通过对参数的显式. tostring()调用来删除那些特定的装箱分配。

减少关闭分配

只分配闭包一次,并存储结果,就可供多次重复使用。例如,通常将委托传递给ConcurrentDictionary. getoradd。与其将委托编写为内联lambda,还不如将define定义为类中的私有字段。下面是来自Hagar中可选ISerializable支持包的一个例子:

  1. private readonly Func<Type, Action<object, SerializationInfo, StreamingContext>> createConstructorDelegate; 
  2.  
  3. public ObjectSerializer(SerializationConstructorFactory constructorFactory) 
  4.     // Other parameters/statements omitted. 
  5.     this.createConstructorDelegate = constructorFactory.GetSerializationConstructorDelegate; 
  6.  
  7. // Later, on a hot code path: 
  8. var constructor = this.constructors.GetOrAdd(info.ObjectType, this.createConstructorDelegate); 

尽量减少复制

.NET Core 2.0和2.1以及最近的C#版本,在删除数据复制过程的方面取得了相当大的进步。最值得注意的是Span,但在参数修饰符和只读结构中也值得一提。Span 是ref 结构堆栈,而不是托管堆上分配。

使用Span来避免数组分配并避免数据复制

一个Span表示任意内存的相邻区域, 一个Span实例通常用来保存数组的元素或数组的一部分。

对于.NET Core来说,Span对于性能优化非常重要,它们使用优化的表示来减小它们的大小,这需要添加对内部指针的垃圾收集器的支持。内部指针是指向数组范围内的托管引用,而不是只能指向第一个元素,因此需要一个包含数组偏移量的附加字段。有关Span的更多信息,请点此参考。

Hagar广泛使用Span,因为它允许我们创建可用于较大缓冲区的分段试图。

通过ref传递结构以最小化堆栈上的副本

Hagar使用两个主要结构,Reader 和Writer。这些结构包含几个字段,几乎每次调用都会传递给序列化或反序列化调用路径。

在没有干预的情况下,使用这些结构进行的每个方法调用都会带来很大的影响,因为每个调用都需要将整个结构复制到堆栈中。

我们可以通过将这些结构作为ref参数传递来避免副本的产生,另外,C#还支持使用ref this作为扩展方法的目标,这非常方便。据我所知,没有办法确保特定的结构类型总是由ref传递,如果你不小心在调用的参数列表中省略了ref,这可能会导致运行错误。

避免保护性拷贝(defensive copy)

Roslyn有时需要做一些工作来保证一些语言不变量,当结构存储在只读字段中时,编译器将插入一些指令,以避免复制该字段,然后再将其包含到任何能保证不会对其进行修改的操作中。通常,这意味着调用在结构类型本身上定义的方法,因为将结构作为参数传递给在另一类型上定义的方法已经需要将结构复制到堆栈上(除非通过ref或in传递)。

如果将 7.2 添加到csproj文件中,则可以将结构定义为只读结构(这是c# 7.2的语言特性),则可以避免保护性拷贝。

有时,如果你无法将其定义为只读结构,则最好在其他不可变结构字段上省略readonly修饰符。

以Jon Skeet的NodaTime库为例,在这个示例中,Jon使大多数结构变为只读,因此能够将readonly修饰符添加到包含这些结构的字段中,而不会对性能产生负面影响。

减少分支和分支错误预测

现代cpu依赖于长pipeline的指令,这些指令通过并发性进行处理。这涉及到CPU分析指令,以确定哪些指令不依赖于前面的指令,还涉及猜测将采用哪些条件跳转语句。为此,CPU使用一个名为分支预测器(branch predictor)的组件,该组件负责猜测将采用哪个分支。它通常通过读取和写入表中的条目来实现这一点,并根据上次执行条件跳转时发生的情况修改其预测。

当预测正确时,就会加快进程,否则就需要把预测分支的指令排空,重新获取正确分支的指令进入pipeline继续执行。

所以加快进程的最好办法就是减少分支和分支错误预测,首先尝试最小化分支数量,如果无法消除分支,请尽量减少错误预测率,这可能涉及使用排序数据或重构代码,可以用查找的办法来代替分支预测。

其他杂项提示

1. 避免使用LINQ,LINQ在应用程序代码方面很出色,但在库/框架代码中很少被用于路径。LINQ很难对JIT进行优化(IEnumerable..),而且倾向于多多分配。

2. 使用具体类型而不是接口或抽象类型,也许最常见的是,如果你在List 上进行迭代,最好不要先将该列表强制转换为IEnumerable (例如,通过使用LINQ或将其作为IEnumerable 参数传递给方法)。这样做的原因是使用foreach枚举列表使用非分配List .Enumerator结构,但是当它转换为IEnumerable 时,该结构必须被装箱到IEnumerator for foreach。

3. 反射在库代码中特别有用,缓存反射结果,考虑使用IL或Roslyn为访问器生成委托,或者更好的方法是使用现有的库,如Microsoft.Extensions.ObjectMethodExecutor.Sources,Microsoft.Extensions.PropertyHelper.Sources或FastMember。

特定于库的优化

优化生成的代码

Hagar使用Roslyn为要序列化的POCO生成C#代码,这个C#代码在编译时包含在你的项目中。我们可以对生成的代码执行一些优化,以加快速度。

通过跳过对已知类型的编解码器查找来避免虚拟调用

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读