.NET高性能类及其优化技巧

高性能类

Span 和 Memory:快速访问内存的方法,无需进行不必要的复制操作。

Memory是什么?
它是一种可变大小、可读写的内存块,可以安全地暴露给用户代码进行操作。

  • Memory是可变的,所以我们可以直接在内存中操作数据,而不需要进行额外的拷贝操作。例如,当你需要从一个字节数组中获取一个子数组时,传统做法可能需要先分配一个新的数组,并将原数组中的数据复制到新数组中,而使用Memory则可以直接创建一个指向原数组的Memory对象,并通过切片等操作来获取子数组,避免了不必要的内存分配和拷贝。
  • 使用Memory还可以减少垃圾回收的压力,因为我们不需要创建新的对象来存储数据。
  • Memory还可以与Span和ReadOnlySpan类型一起使用,这些类型可以方便地对数据进行访问和操作。
Memory<byte> emptyMemory = Memory<byte>.Empty; // 创建一个空内存块
byte[] byteArray = new byte[10]; 
Memory<byte> memory = byteArray; // 数组=>内存块
byte[] array = memory.Span.ToArray(); // 内存块=>数组
Memory<byte> slice = memory.Slice(2, 2); // 内存切片

注意:当你将byte[]数组传递给Memory的构造函数或者使用AsMemory()方法时,Memory会引用同一块内存,而不会进行内存复制。这意味着对Memory的修改会直接反映在原始的byte[]数组上,反之亦然。

byte[] byteArray = new byte[] { 0x12, 0x34, 0x56, 0x78, 0x9A };
Memory<byte> byteMemory = byteArray.AsMemory();
// 修改Memory<byte>
byteMemory.Span[0] = 0xAB;
Console.WriteLine(byteArray[0]);  // 输出:0xAB

System.IO.Pipelines:用于高性能 I/O。

ValueTask:轻量级任务类型,表示可能异步完成的操作。

System.Buffers:该库提供了一组用于创建和管理缓冲区的类。

System.Numerics:该库提供了一组用于处理大型整数的类。

System.Collections.Frozen不可变只读集合,它的创建成本相对较高,但提供出色的查找性能。

Pool池类

System.Buffers.ArrayPool
System.Buffers.MemoryPool
Microsoft.Extensions.ObjectPool.ObjectPool
System.Threading.ThreadPool / Task

使用 ref struct 做到 0 GC

使用 unsafe

代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。不安全代码或非托管代码是指使用了指针变量的代码块。

细节优化技巧

  • 使用简单的数据结构:尽量使用简单的数据结构,如数组而不是列表(List)、结构体而不是类等。
  • 避免装箱和拆箱:尽量避免值类型和引用类型之间的转换,因为这会引入额外的开销。
  • 使用不可变类型:不可变类型可以减少并发环境下的竞争和复杂性,提高性能。
  • 使用内存池:适当地重用对象,可以通过内存池来避免频繁地分配和回收内存。
  • 避免使用反射:反射操作会比直接调用方法或访问属性慢很多,尽量避免在性能敏感的代码路径中使用反射。
  • 使用并行编程:在需要处理大量数据或进行并行计算时,可以考虑使用并行编程库,如 Parallel 类或异步编程模式。
  • 优化关键路径:对于性能关键的代码路径,进行适当的优化,比如减少循环内部的计算、减少内存分配等。
  • 使用性能分析工具:利用性能分析工具(如 Profiler)来找出性能瓶颈,并有针对性地进行优化。
  • 使用StringBuilder而不是String:在C#中,字符串是不可变的。这意味着每次对字符串进行修改时,都会生成一个新的字符串对象。这可能会导致大量不必要的内存分配和垃圾回收。对于需要多次修改字符串的情况,最好使用StringBuilder类,这是一个可变对象,可以更有效地处理字符串。
  • 使用异步编程:异步编程可以让您在等待某些操作(如I/O操作)完成时,同时执行其他操作。在C#中,async和await关键字是处理异步操作的常用方式。
  • 合适的数据结构和算法:选择正确的数据结构和算法可以极大地提高代码的性能。例如,如果您需要频繁地在列表中查找元素,那么可能应该使用HashSet而不是List。

其他优化

  • .NET 5引入了AOT,.NET Native是一个AOT编译器,通过预先将.NET应用程序编译为本地机器代码,加快了应用程序的启动时间和执行效率。
  • PGO (Profile-Guided Optimization) 是使用配置文件引导的优化,是一种编译器优化技术,借助配置文件来引导编译,达到提高程序运行时性能的目的。PGO通过收集运行时信息来指导JIT如何优化代码,相比以前没有PGO时可以做更多以前难以完成的优化。这项优化是一个通用技术,不局限于某一门语言。

扩展阅读

【译】ASP.NET Core 6 中的性能改进
ASP.NET Core 7 中的性能改进
【译】.NET 7 中的性能改进
.NET高性能编程Span/Memory
在 C# 中使用 Span 和 Memory 编写高性能代码

此处评论已关闭