加入收藏 | 设为首页 | 会员中心 | 我要投稿 辽源站长网 (https://www.0437zz.com/)- 云专线、云连接、智能数据、边缘计算、数据安全!
当前位置: 首页 > 运营中心 > 建站资源 > 优化 > 正文

.NET 性能优化的技巧

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

最大化内联

内联是将方法体(method body)复制到调用站点的技术,这样我们就可以避免跳转、参数传递和寄存器保存/恢复等繁琐过程。除了节省这些之外,内联还是实现其他优化的必要条件。 不不过Roslyn(C#的编译器)没有内联代码,它是通过JIT实现的,大多数优化也是如此。

.NET 性能优化的技巧

使用静态投掷助手(static throw helper)

最近的变化涉及一个重要的重构,在序列化基准的调用持续时间上增加了大约20ns,从~130ns增加到了~150ns。

罪魁祸首是这个助手方法中添加的throw语句:

  1. public static Writer<TBufferWriter> CreateWriter<TBufferWriter>( 
  2.     this TBufferWriter buffer, 
  3.     SerializerSession session) where TBufferWriter : IBufferWriter<byte> 
  4.     if (session == null) throw new ArgumentNullException(nameof(session)); 
  5.     return new Writer<TBufferWriter>(buffer, session); 

当助手方法中包含throw语句时,JIT不会内联它。解决这个问题的常见技巧是添加一个静态的“throw helper”方法,来完成一些棘手的工作,所以最终结果如下所示:

  1. public static Writer<TBufferWriter> CreateWriter<TBufferWriter>( 
  2.     this TBufferWriter buffer, 
  3.     SerializerSession session) where TBufferWriter : IBufferWriter<byte> 
  4.     if (session == null) ThrowSessionNull(); 
  5.     return new Writer<TBufferWriter>(buffer, session); 
  6.  
  7.     void ThrowSessionNull() => throw new ArgumentNullException(nameof(session)); 

代码库在许多地方使用这个技巧,将throw语句放在一个单独的方法中可能会有其他好处,例如比如改善常用代码路径的位置。

最小化虚拟或接口调用

虚拟调用比直接调用慢,如果你正在编写一个关键系统,那么很可能会在分析器中看到虚拟调用的过程。首先,虚拟调用需要间接调用。

去虚拟化是许多JIT编译器的一个特性,RyuJIT也不例外。然而,这是一个复杂的功能,并且RyuJIT目前可以证明(自身)方法可以被虚拟化并因此成为内联的候选者的情况并不多。以下是利用虚拟化的一些常规技巧,但我确信还有更多。

1. 默认情况下将类标记为sealed,当一个类/方法被标记为sealed时,RyuJIT可以将其考虑在内并且可能能够内联一个方法调用。RyuJIT很可能成为下一代的JIT编译器。64位计算已是大势所趋,即使它并不总是比32位更快或更有效率。当前的.NET JIT编译器就是一个使得64位计算机上有时导致程序速度减慢的的例子。但是,这将会被改变:一个新的,下一代x64的JIT编译器编译代码的速度将加快两倍,它将改变你对64位.NET代码的印象。

2. 如果可能,将覆盖(override)方法标记为sealed。override可以翻译为覆盖,从字面就可以知道,它是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。

3. 使用具体类型而不是接口,具体类型为JIT提供了更多信息,因此它更有可能内联你的调用。

4. 在同一方法中实例化和使用非sealed对象(而不是使用'create'方法),当类型明确已知时,比如构造之后,RyuJIT可以对非sealed方法调用进行虚拟化。

5. 对多态类型使用泛型类型约束,以便可以使用具体类型对它们进行专门处理,并且可以对接口调用进行非虚拟化。在Hagar中,我们的核心编写器类型定义如下:

  1. public ref struct Writer<TBufferWriter> where TBufferWriter : IBufferWriter<byte> 
  2.     private TBufferWriter output; 
  3.     // --- etc --- 

所有对CIL中Roslyn发出的输出方法的调用之前都会有一条约束指令,该指令告诉JIT,该调用可以对TBufferWriter上定义的精确方法进行调用,而不是进行虚拟/接口调用。这有助于去虚拟化。结果,所有对在输出上定义的方法的调用都被成功地去虚拟化。下面是由JIT团队的Andy Ayers 编写的CoreCLR线程,它详细描述了当前和未来的去虚拟化工作。

减少分配

.NET的垃圾收集器是一项很伟大的项目, 垃圾收集器是 允许对一些无锁数据结构进行算法优化,并且还可以删除整个类的错误并减轻开发人员的认知负担。总之,垃圾收集是一种非常成功的内存管理技术。

.NET使用bump分配器,其中每个线程通过找到各自的指针来从每个线程上下文中分配对象。因此,当在同一线程上分配和使用短期分配时,可以更好的实现局部缓存(cache locality)机制。

有关.NET 垃圾收集器的更多信息,请点此了解。

对象池(Object Pool) 或缓冲池(Buffer Pool)

Hagar本身并不管理缓冲区,而是将责任转移给用户。这听起来可能很麻烦,但实际上并不麻烦,因为它与System.IO.Pipelines兼容。因此,我们可以利用默认管道通过System.Buffers.ArrayPool 提供的高性能缓冲池。

一般来说,重复使用缓冲区可以减轻垃圾收集器的压力。

避免装箱

(编辑:辽源站长网)

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

推荐文章
    热点阅读