性能优化不是”过早优化”,而是基于数据的科学工程实践。.NET 生态提供了世界一流的诊断工具链——从 BenchmarkDotNet 到 OpenTelemetry,从 dotnet-trace 到 PerfView。本文将系统性地介绍性能分析、诊断工具和优化策略。
性能分析基础
基准测试(BenchmarkDotNet)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running;
[MemoryDiagnoser] [DisassemblyDiagnoser] [RankColumn] public class StringBenchmarks { private readonly string _text = "hello world"; private readonly int _iterations = 1000;
[Benchmark(Baseline = true)] public bool ContainsChar() { return _text.Contains('o'); }
[Benchmark] public bool IndexOfChar() { return _text.IndexOf('o') >= 0; }
[Benchmark] public bool ManualLoop() { for (int i = 0; i < _text.Length; i++) if (_text[i] == 'o') return true; return false; } }
BenchmarkRunner.Run<StringBenchmarks>();
|
性能计数器(dotnet-counters)
1 2 3 4 5 6
| dotnet tool install --global dotnet-counters
dotnet-counters monitor --process-id 1234 --refresh-interval 1 \ System.Runtime[cpu-usage,gc-heap-size,time-in-gc]
|
跟踪(dotnet-trace)
1 2 3 4 5 6 7
| dotnet tool install --global dotnet-trace
dotnet-trace collect --process-id 1234 --profile gc-verbose --duration 00:00:30
|
内存优化策略
减少分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
var filtered = list.Where(x => x > 10).ToList();
var result = new List<int>(list.Count); foreach (var x in list) if (x > 10) result.Add(x);
string s = ""; for (int i = 0; i < 1000; i++) s += i;
var sb = new StringBuilder(); for (int i = 0; i < 1000; i++) sb.Append(i);
decimal discounts = products.Sum(p => CalculateDiscount(p, GetCurrentDate())); dozen more uses of GetCurrentDate()...
var today = GetCurrentDate(); decimal discounts = products.Sum(p => CalculateDiscount(p, today));
var buffer = ArrayPool<byte>.Shared.Rent(4096); try { Process(buffer); } finally { ArrayPool<byte>.Shared.Return(buffer); }
|
结构体优化
1 2 3 4 5 6 7 8 9 10 11 12
|
public void ProcessPoint(Point p) { }
public void ProcessPoint(in Point p) { }
int x = ((IComparable)42).CompareTo(10);
public void Compare<T>(T a, T b) where T : IComparable<T> { }
|
对象池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using Microsoft.Extensions.ObjectPool;
public class MyPooledObject { public int Data { get; set; } public void Reset() => Data = 0; }
var pool = new DefaultObjectPool<MyPooledObject>( new DefaultPooledObjectPolicy<MyPooledObject>());
var obj = pool.Get(); try { obj.Data = 42; } finally { pool.Return(obj); }
|
CPU 优化
热路径优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
for (int i = 0; i < collection.Items.Count; i++) { Process(collection.Items[i]); }
var items = collection.Items; for (int i = 0; i < items.Count; i++) Process(items[i]);
static string GetName(int id) => id switch { 1 => "One", 2 => "Two", 3 => "Three", _ => "Unknown" };
var threshold = 50; var filtered = list.Where(x => x > threshold);
var filtered = list.Where(x => x > 50);
|
SIMD 向量化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using System.Numerics;
static float SumVectorized(float[] values) { var sum = Vector<float>.Zero; int i = 0;
for (; i <= values.Length - Vector<float>.Count; i += Vector<float>.Count) { sum += new Vector<float>(values, i); }
float total = Vector.Dot(sum, Vector<float>.One);
for (; i < values.Length; i++) total += values[i];
return total; }
|
字符串优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| using System.Buffers;
private static readonly SearchValues<char> _separators = SearchValues.Create([',', ';', '|']);
bool IsSeparator(char c) => _separators.Contains(c);
ReadOnlySpan<char> span = "a,b,c"; while (span.Length > 0) { int index = span.IndexOfAny(_separators); var segment = index < 0 ? span : span[..index]; Console.WriteLine(segment.ToString()); span = index < 0 ? [] : span[(index + 1)..]; }
|
异步优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public async Task<int> GetValueAsync() { return await Task.Run(() => Compute()); }
public int GetValueSync() => Compute();
public ValueTask<int> GetCachedAsync() { if (_cached.HasValue) return new ValueTask<int>(_cached.Value); return new ValueTask<int>(FetchAsync()); }
await Task.Delay(100).ConfigureAwait(false);
|
诊断与日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using OpenTelemetry; using OpenTelemetry.Trace;
using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("MyApp") .AddConsoleExporter() .AddJaegerExporter() .Build();
using var activity = MyActivitySource.StartActivity("ProcessOrder"); activity?.SetTag("order.id", order.Id); activity?.AddEvent(new ActivityEvent("开始处理"));
using Microsoft.Extensions.Logging;
var logger = loggerFactory.CreateLogger<OrderService>(); logger.LogInformation("处理订单 {OrderId},金额 {Amount}", order.Id, order.Amount);
|
.NET 性能清单
| 分类 |
优化项 |
预期收益 |
| 内存 |
使用 ArrayPool/对象池 |
减少 GC 压力 50-80% |
| 内存 |
值类型代替引用类型 |
减少堆分配,提升缓存友好性 |
| 内存 |
使用 Span/Memory |
零分配切片 |
| CPU |
SIMD 向量化 |
数据并行加速 2-8x |
| CPU |
使用 SearchValues |
字符搜索加速 3-5x |
| 异步 |
ValueTask 替换 Task |
减少堆分配 |
| 并发 |
ConcurrentDictionary |
减少锁竞争 |
| IO |
HttpClient 连接池 |
减少握手开销 |
实际优化流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var stopwatch = Stopwatch.StartNew(); DoWork(); stopwatch.Stop(); Console.WriteLine($"耗时: {stopwatch.ElapsedMilliseconds}ms");
var summary = BenchmarkRunner.Run<MyBenchmark>();
|
总结:性能优化是一个持续迭代的过程——测量、定位、优化、再测量。.NET 提供了从 BenchmarkDotNet 到 OpenTelemetry 的完整工具链,让每一个优化决策都有据可依。常见的优化策略包括减少内存分配(池化、Span、值类型)、利用 SIMD 向量化、优化热路径避免闭包和重复属性访问。记住:始终基于数据做优化,不要让猜想代替测量,也不要为了牛毛般的性能提升而牺牲代码的清晰度。