返回合集C# 高性能写法第 1 / 5 篇
C# 高性能写法(一):通用优化
1. ⏱️ Stopwatch
1.1 测量执行时间
var stopwatch = Stopwatch.StartNew();
Run();
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
- 测量前先预热待测代码。
- 使用相同输入执行多轮,比较平均值或中位数。
- 日志使用
ElapsedMilliseconds,短耗时使用Elapsed.TotalMilliseconds。
1.2 记录时间戳 推荐
long start = Stopwatch.GetTimestamp();
Run();
TimeSpan elapsed = Stopwatch.GetElapsedTime(start);
需要比较分配量、GC 和稳定吞吐量时,改用 BenchmarkDotNet。
2. 📚 stackalloc
2.1 创建小型临时缓冲区
Span<int> buffer = stackalloc int[128];
Fill(buffer);
Consume(buffer);
2.2 动态长度设置回退
const int StackLimit = 256;
Span<byte> buffer = length <= StackLimit
? stackalloc byte[length]
: new byte[length];
大型且频繁使用的缓冲区改用 ArrayPool<T>。
2.3 使用限制
- 只分配容量明确的小缓冲区。
- 不要在循环中反复执行较大的
stackalloc。 - 内存读取前必须完整写入,敏感数据使用后主动清理。
- 由它创建的
Span<T>不能逃离当前方法,也不能跨越await或yield使用。
3. 🧩 使用 static Lambda 避免捕获
3.1 无需外部状态时添加 static
var enabledUsers = users.Where(
static user => user.IsEnabled);
3.2 需要外部值时保留捕获
int minimumScore = GetMinimumScore();
var passed = students.Where(
student => student.Score >= minimumScore);
- 不需要外部状态时使用
staticLambda。 - 需要动态外部值时,保留捕获或使用支持状态参数的 API。
- 热路径中的捕获可以改为显式循环或独立方法。
staticLambda 不能捕获局部变量、方法参数或this,但可以访问常量和静态成员。- 非捕获 Lambda 可能已被编译器缓存,是否有收益仍需测量。
4. ⏲️ 使用 PeriodicTimer 执行周期任务
4.1 复用定时器执行异步任务
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await RefreshAsync(stoppingToken);
}
长期运行的周期任务复用同一个 PeriodicTimer,避免在循环中反复创建 Task.Delay。
4.2 与 Task.Delay 的区别
| 写法 | 间隔语义 | 适用场景 |
|---|---|---|
PeriodicTimer | 固定频率,实际间隔约为 max(周期, 任务耗时) | 心跳、指标采集、缓存刷新 |
Task.Delay | 固定延迟,实际间隔约为 任务耗时 + Delay | 轮询、冷却、重试间隔 |
需要在任务完成后再等待完整间隔时,直接使用 Task.Delay:
while (!stoppingToken.IsCancellationRequested)
{
await RefreshAsync(stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
- 希望按固定节奏执行,并且允许跳过来不及处理的 Tick,使用
PeriodicTimer。 - 希望每次任务完成后休息固定时间,使用
Task.Delay。 PeriodicTimer会等待第一个 Tick;上面的Task.Delay写法会先执行一次任务。
4.3 使用限制
- 同一个
PeriodicTimer同时只能有一个等待者。 - 取消令牌只取消当前等待,停止定时器需要调用
Dispose()或离开using作用域。 - 处理速度慢于周期时,多个 Tick 会合并,不适合要求每次触发都必须执行的任务。
- 循环中的任务按顺序执行,不会产生定时器回调重入。
- 不适合实时系统或需要高精度调度的任务。
- 低频任务中的性能差异通常很小,是否替换仍需测量。