引言:为什么 C# 性能很重要?
在现代软件开发世界中,应用程序的运行速度直接影响用户体验、基础设施成本和产品竞争力。.NET 平台和 C# 语言提供了创建高性能系统的强大工具,然而,不当使用功能(例如,不受控制的内存操作或低效算法)可能导致严重的延迟和资源过度消耗。在本文中,我们将分析 C# 应用程序性能优化的关键方法,这些方法将帮助您编写更快、更高效的代码。
1. 性能分析:在优化之前找到瓶颈
在进行更改之前,必须准确确定时间花费最多的地方。没有性能分析,您可能会优化那些本已运行良好的部分,而忽略了真正的“瓶颈”。
性能分析工具
- Visual Studio Diagnostic Tools — 用于分析 CPU、内存和异常的内置分析器。
- dotMemory — 用于分析内存管理和查找泄漏的强大工具。
- PerfView — 来自 Microsoft 的免费工具,用于深入分析 .NET 性能。
- BenchmarkDotNet — 用于对代码片段进行微基准测试的库。
使用 BenchmarkDotNet 测量两种方法速度的示例:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public class StringBenchmark
{
private string[] data = Enumerable.Range(0, 1000).Select(i => $"Item {i}").ToArray();
[Benchmark]
public string ConcatenateWithPlus()
{
string result = "";
foreach (var s in data) result += s;
return result;
}
[Benchmark]
public string ConcatenateWithStringBuilder()
{
var sb = new System.Text.StringBuilder();
foreach (var s in data) sb.Append(s);
return sb.ToString();
}
}
// 运行:BenchmarkRunner.Run<StringBenchmark>();
基准测试结果将清楚地表明,在循环中 StringBuilder 比通过 + 进行字符串连接快数十倍。
2. 内存管理与降低 GC 负载
垃圾回收器 (Garbage Collector) 是一个强大的机制,但其频繁工作可能会对性能产生负面影响,尤其是在高负载系统中。主要目标是尽量减少分配次数和对象的生命周期。
2.1. 使用结构体 (struct) 代替类
如果对象较小(小于 16-24 字节)且生命周期短,请考虑使用 struct。结构体存储在栈上(或嵌入到父对象中),不会给 GC 带来负载。
// 类 — 在堆上分配
public class PointClass
{
public int X { get; set; }
public int Y { get; set; }
}
// 结构体 — 在栈上
public struct PointStruct
{
public int X;
public int Y;
}
2.2. 对象池 (Object Pooling)
对于频繁创建和销毁的对象(例如,数据库连接、缓冲区),请使用对象池。在 .NET Core 中有内置的实现 ObjectPool<T>。
using Microsoft.Extensions.ObjectPool;
var pool = new DefaultObjectPool<List<int>>(new ListPolicy());
var list = pool.Get();
try
{
// 使用列表
}
finally
{
pool.Return(list); // 返回池中
}
2.3. Span<T> 和 Memory<T>
这些类型允许在不进行额外分配的情况下处理连续的内存区域。它们非常适合解析、处理数组和字符串。
public int SumDigits(ReadOnlySpan<