.NET性能优化实战
大约 17 分钟约 5006 字
.NET性能优化实战
简介
性能优化是后端开发的高级技能,直接影响用户体验和运营成本。本文从性能分析工具、异步编程最佳实践、内存优化、GC 调优、缓存策略、数据库查询优化、响应压缩和基准测试八个维度,系统介绍 .NET 应用的性能优化方法和实践。核心原则是:先测量,再优化,用数据说话。
性能分析工具
dotnet-trace — 性能追踪
# 安装 .NET 诊断工具
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-dump
# 收集 CPU 性能数据(生成 speedscope 格式,可用浏览器查看)
dotnet-trace collect -p <pid> --format speedscope
# 收集特定 Provider 的数据
dotnet-trace collect -p <pid> --providers Microsoft-DotNETCore-SampleProfiler
# 实时监控运行时指标
dotnet-counters monitor -p <pid>
# 监控自定义指标
dotnet-counters monitor -p <pid> --counters System.Runtime,Microsoft.AspNetCore.Hostingdotnet-counters — 实时监控
/// <summary>
/// 使用 EventCounters 自定义性能指标
/// 可以通过 dotnet-counters 实时查看
/// </summary>
[EventSource(Name = "MyApp-Performance")]
public sealed class AppPerformanceSource : EventSource
{
public static AppPerformanceSource Log = new();
// 自定义计数器
private readonly IncrementingEventCounter _requestCounter;
private readonly EventCounter _requestDuration;
public AppPerformanceSource()
{
_requestCounter = new IncrementingEventCounter("requests-per-second", this);
_requestDuration = new EventCounter("request-duration-ms", this);
}
/// <summary>记录一次请求</summary>
public void RequestStarted() => _requestCounter.Increment();
/// <summary>记录请求耗时</summary>
public void RequestCompleted(double durationMs) => _requestDuration.WriteMetric(durationMs);
protected override void Dispose(bool disposing)
{
_requestCounter?.Dispose();
_requestDuration?.Dispose();
base.Dispose(disposing);
}
}
// 在中间件中使用
app.Use(async (context, next) =>
{
AppPerformanceSource.Log.RequestStarted();
var sw = Stopwatch.StartNew();
await next(context);
sw.Stop();
AppPerformanceSource.Log.RequestCompleted(sw.Elapsed.TotalMilliseconds);
});dotnet-dump — 内存分析
# 生成内存转储
dotnet-dump collect -p <pid>
dotnet-dump collect -p <pid> --type Full # 完整转储
dotnet-dump collect -p <pid> --type Heap # 仅堆内存
# 分析内存转储
dotnet-dump analyze dump.dmg
# 常用分析命令(在 analyze 交互模式下)
# dumpheap -stat 查看堆上所有对象的统计信息
# dumpheap -mt <MT> 查看指定类型的所有对象
# gcroot <address> 查看对象的 GC 根引用链(排查内存泄漏)
# dumpgen <generation> 查看指定代的所有对象
# clrstack 查看当前线程的调用栈
# setthread <threadId> 切换到指定线程async/await 最佳实践
避免阻塞调用
/// <summary>
/// 异步编程常见错误与正确做法对比
/// </summary>
public class AsyncBestPractices
{
// ===== 错误示例 =====
// 错误1:使用 .Result 或 .Wait() 阻塞异步操作
public User GetUser_Bad(int id)
{
return _userService.GetUserAsync(id).Result;
// 在 ASP.NET Core 中会导致线程饥饿
// 在 WPF 中可能导致死锁
}
// 错误2:async void(仅事件处理器可以使用)
public async void SaveData_Bad()
{
await _repository.SaveAsync(); // 异常无法被调用方捕获
}
// 错误3:不必要的 async/await(增加状态机开销)
public async Task<string> GetName_Bad(int id)
{
return await _cache.GetStringAsync($"user:{id}:name");
// 如果只是直接返回,不需要 async/await
}
// ===== 正确做法 =====
// 正确1:一路 async/await
public async Task<User> GetUser_Good(int id)
{
return await _userService.GetUserAsync(id);
}
// 正确2:返回 Task 而不是 async void
public async Task SaveData_Good()
{
await _repository.SaveAsync();
}
// 正确3:直接返回 Task(减少状态机开销)
public Task<string> GetName_Good(int id)
{
return _cache.GetStringAsync($"user:{id}:name");
}
}并行异步操作
/// <summary>
/// 并行执行多个异步任务
/// </summary>
public class ParallelAsyncService
{
/// <summary>
/// 串行 vs 并行 — 3个各耗时100ms的操作
/// 串行:300ms,并行:100ms
/// </summary>
public async Task<DashboardData> GetDashboardAsync()
{
// ===== 错误:串行执行 =====
// var users = await GetUsersAsync(); // 100ms
// var orders = await GetOrdersAsync(); // 100ms
// var stats = await GetStatsAsync(); // 100ms
// 总耗时:300ms
// ===== 正确:并行执行 =====
var usersTask = GetUsersAsync();
var ordersTask = GetOrdersAsync();
var statsTask = GetStatsAsync();
await Task.WhenAll(usersTask, ordersTask, statsTask);
// 总耗时:100ms
return new DashboardData
{
Users = await usersTask,
Orders = await ordersTask,
Stats = await statsTask
};
}
/// <summary>
/// 带超时和取消的并行操作
/// </summary>
public async Task<List<string>> FetchAllWithTimeoutAsync(List<string> urls, int timeoutMs = 5000)
{
using var cts = new CancellationTokenSource(timeoutMs);
var tasks = urls.Select(url =>
_httpClient.GetStringAsync(url, cts.Token)).ToArray();
try
{
var results = await Task.WhenAll(tasks);
return results.ToList();
}
catch (OperationCanceledException)
{
// 超时处理
throw new TimeoutException($"请求在 {timeoutMs}ms 内未完成");
}
}
/// <summary>
/// 限制并发数量 — 使用 SemaphoreSlim
/// </summary>
public async Task<List<T>> ProcessInParallelAsync<T>(
IEnumerable<T> items,
Func<T, Task> processFunc,
int maxConcurrency = 10)
{
using var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = items.Select(async item =>
{
await semaphore.WaitAsync();
try
{
await processFunc(item);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
return items.ToList();
}
}ConfigureAwait 使用场景
/// <summary>
/// ConfigureAwait 的正确使用
/// </summary>
public class ConfigureAwaitUsage
{
// ===== 库代码/后台服务 — 使用 ConfigureAwait(false) =====
// 避免捕获 SynchronizationContext,减少线程切换开销
public async Task<string> GetDataFromApiAsync(string url)
{
var response = await _httpClient.GetAsync(url)
.ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
return content;
}
// ===== ASP.NET Core — 不需要 ConfigureAwait(false) =====
// ASP.NET Core 没有 SynchronizationContext,不需要
public async Task<ActionResult> GetUsers()
{
var users = await _userService.GetAllAsync();
return Ok(users);
}
// ===== WPF/WinForms — UI 代码不能用 ConfigureAwait(false) =====
// 需要回到 UI 线程更新控件
public async Task LoadDataAsync()
{
var data = await _service.GetDataAsync(); // 自动回到 UI 线程
DataGrid.ItemsSource = data; // 安全更新 UI
}
}内存优化
Span 和 Memory
/// <summary>
/// Span<T> — 零拷贝操作内存区域
/// Span 只能存在于栈上,不能作为类字段或用于 async 方法
/// Memory<T> 可以在堆上使用,适合 async 场景
/// </summary>
public class SpanAndMemory
{
/// <summary>
/// 字符串解析优化
/// </summary>
public class TextParser
{
// 传统方式 — 多次分配新字符串
public (string Version, string Build) Parse_Old(string input)
{
var parts = input.Split('-'); // 分配数组
return (parts[0], parts[1]); // 分配两个字符串
}
// Span 方式 — 零分配
public (ReadOnlySpan<char> Version, ReadOnlySpan<char> Build) Parse_Fast(ReadOnlySpan<char> input)
{
var separator = input.IndexOf('-');
return (input.Slice(0, separator), input.Slice(separator + 1));
}
/// <summary>
/// 使用 Span 解析 HTTP 请求行
/// GET /api/users?page=1 HTTP/1.1
/// </summary>
public static void ParseRequestLine(ReadOnlySpan<char> line)
{
var spaceIndex = line.IndexOf(' ');
var method = line.Slice(0, spaceIndex);
var rest = line.Slice(spaceIndex + 1);
spaceIndex = rest.IndexOf(' ');
var path = rest.Slice(0, spaceIndex);
var version = rest.Slice(spaceIndex + 1);
Console.WriteLine($"Method: {method}, Path: {path}, Version: {version}");
}
}
/// <summary>
/// 二进制数据处理
/// </summary>
public class BinaryProcessor
{
/// <summary>
/// 解析二进制协议数据包(零拷贝)
/// </summary>
public static void ParsePacket(ReadOnlySpan<byte> packet)
{
// 协议格式:[Header 4字节][Length 2字节][Payload N字节][CRC 2字节]
var header = packet.Slice(0, 4);
var length = BitConverter.ToInt16(packet.Slice(4, 2));
var payload = packet.Slice(6, length);
var crc = packet.Slice(packet.Length - 2, 2);
}
/// <summary>
/// 使用 stackalloc 在栈上分配内存(避免堆分配)
/// </summary>
public static int ProcessSmallBuffer()
{
Span<byte> buffer = stackalloc byte[256]; // 栈上分配,无需 GC
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)(i & 0xFF);
}
return buffer[0];
}
}
}对象池
/// <summary>
/// 对象池 — 复用对象减少 GC 压力
/// 适用于频繁创建和销毁的昂贵对象
/// </summary>
public class PoolExamples
{
/// <summary>
/// StringBuilder 对象池
/// </summary>
public class StringBuilderPool
{
private readonly ObjectPool<StringBuilder> _pool =
ObjectPool.Create<StringBuilder>();
public string BuildCsv<T>(IEnumerable<T> items, Func<T, string> selector)
{
var sb = _pool.Get();
try
{
sb.Clear();
foreach (var item in items)
{
sb.AppendLine(selector(item));
}
return sb.ToString();
}
finally
{
_pool.Return(sb);
}
}
}
/// <summary>
/// ArrayPool — 复用大数组
/// </summary>
public class ArrayPoolExample
{
public byte[] CompressData(byte[] inputData)
{
// 从池中租用缓冲区,而不是 new byte[size]
var outputBuffer = ArrayPool<byte>.Shared.Rent(inputData.Length * 2);
try
{
// 执行压缩操作...
var compressedLength = DoCompress(inputData, outputBuffer);
// 只返回实际使用的数据
return outputBuffer.AsSpan(0, compressedLength).ToArray();
}
finally
{
// 归还数组到池中
ArrayPool<byte>.Shared.Return(outputBuffer, clearBuffer: true);
}
}
/// <summary>
/// 处理流数据的通用模式
/// </summary>
public async Task ProcessStreamAsync(Stream stream, CancellationToken ct = default)
{
var buffer = ArrayPool<byte>.Shared.Rent(81920); // 80KB 缓冲区
try
{
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), ct)) > 0)
{
ProcessBuffer(buffer.AsSpan(0, bytesRead));
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
private int DoCompress(byte[] input, byte[] output) => 0;
private void ProcessBuffer(ReadOnlySpan<byte> buffer) { }
}
/// <summary>
/// 自定义对象池策略
/// </summary>
public class ExpensiveObjectPool
{
private readonly DefaultObjectPool<ExpensiveResource> _pool;
public ExpensiveObjectPool()
{
var policy = new DefaultPooledObjectPolicy<ExpensiveResource>(() =>
{
// 创建昂贵对象
return new ExpensiveResource();
});
_pool = new DefaultObjectPool<ExpensiveResource>(policy, maximumRetained: 10);
}
public ExpensiveResource Rent() => _pool.Get();
public void Return(ExpensiveResource obj) => _pool.Return(obj);
}
public class ExpensiveResource : IDisposable
{
public void Dispose() { }
}
}GC 调优
GC 模式选择
// runtimeconfig.template.json — 项目级 GC 配置
{
"configProperties": {
// Server GC — 服务器模式(ASP.NET Core 默认)
// 每个核心一个 GC 堆,吞吐量更高
"System.GC.Server": true,
// 并发 GC — 后台回收,减少暂停时间
"System.GC.Concurrent": true,
// 堆数量 — 限制 GC 线程数(适合容器化环境)
"System.GC.HeapCount": 2,
// 堆亲和性 — 绑定 GC 线程到特定 CPU 核心
"System.GC.HeapAffinitizeMask": 3,
// GC 区域(Region) — 替代 LOH/SOH 的分段模型
"System.GC.RegionServers": true
}
}GC 配置策略
| 配置场景 | GC 模式 | 说明 |
|---|---|---|
| Web API 高吞吐 | Server GC + Concurrent | 默认配置,适合大多数场景 |
| 容器环境低内存 | Workstation GC | 内存占用更低 |
| 延迟敏感 | Concurrent GC + HeapCount=核心数 | 减少暂停时间 |
| 微服务小实例 | Server GC + HeapCount=1 | 单核容器优化 |
减少 GC 压力的编码实践
/// <summary>
/// 减少 GC 压力的编码实践
/// </summary>
public class GcFriendlyCode
{
/// <summary>
/// 1. 避免在热路径中分配 — 使用 ValueTask 代替 Task
/// </summary>
public ValueTask<int> GetCachedValueAsync(string key)
{
// 如果缓存命中,直接返回值(不分配 Task 对象)
if (_cache.TryGetValue(key, out int value))
{
return new ValueTask<int>(value); // 零分配
}
// 缓存未命中才走异步路径
return new ValueTask<int>(FetchFromDbAsync(key));
}
/// <summary>
/// 2. 避免装箱 — 使用泛型而不是 object
/// </summary>
// 错误 — 装箱
public int Sum_Old(ArrayList list)
{
int sum = 0;
foreach (object item in list)
{
sum += (int)item; // 装箱 + 拆箱
}
return sum;
}
// 正确 — 泛型
public int Sum_Good(List<int> list)
{
int sum = 0;
foreach (int item in list)
{
sum += item; // 无装箱
}
return sum;
}
/// <summary>
/// 3. 使用 string.Create 减少字符串分配
/// </summary>
public static string FormatPrice(decimal price)
{
return string.Create(20, price, (span, value) =>
{
value.TryFormat(span, out _, "C2");
});
}
/// <summary>
/// 4. 使用集合初始容量 — 避免动态扩容
/// </summary>
public List<string> ProcessItems(List<string> source)
{
// 预分配容量,避免内部数组的多次扩容和复制
var result = new List<string>(source.Count);
foreach (var item in source)
{
result.Add(ProcessItem(item));
}
return result;
}
private readonly Dictionary<string, int> _cache = new();
private async Task<int> FetchFromDbAsync(string key) => 0;
private string ProcessItem(string item) => item;
}缓存策略
多级缓存架构
/// <summary>
/// 多级缓存服务 — 内存缓存 + 分布式缓存
/// L1: IMemoryCache(进程内,最快)
/// L2: Redis(分布式,跨实例共享)
/// L3: 数据库(最慢,数据源)
/// </summary>
public class TieredCacheService
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
private readonly IDataRepository _repository;
private readonly ILogger<TieredCacheService> _logger;
public TieredCacheService(
IMemoryCache memoryCache,
IDistributedCache distributedCache,
IDataRepository repository,
ILogger<TieredCacheService> logger)
{
_memoryCache = memoryCache;
_distributedCache = distributedCache;
_repository = repository;
_logger = logger;
}
/// <summary>
/// 三级缓存读取
/// </summary>
public async Task<T> GetOrSetAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? memoryExpiry = null,
TimeSpan? distributedExpiry = null)
{
memoryExpiry ??= TimeSpan.FromMinutes(5);
distributedExpiry ??= TimeSpan.FromMinutes(30);
// L1:内存缓存
if (_memoryCache.TryGetValue(key, out T memoryValue))
{
_logger.LogDebug("L1缓存命中:{Key}", key);
return memoryValue;
}
// L2:分布式缓存
var distributedValue = await _distributedCache.GetStringAsync(key);
if (distributedValue != null)
{
_logger.LogDebug("L2缓存命中:{Key}", key);
var value = JsonSerializer.Deserialize<T>(distributedValue);
// 回填 L1 缓存
_memoryCache.Set(key, value, memoryExpiry.Value);
return value;
}
// L3:数据源
_logger.LogDebug("缓存未命中,查询数据源:{Key}", key);
var result = await factory();
// 写入 L2
var json = JsonSerializer.Serialize(result);
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = distributedExpiry
};
await _distributedCache.SetStringAsync(key, json, options);
// 写入 L1
_memoryCache.Set(key, result, memoryExpiry.Value);
return result;
}
/// <summary>
/// 失效缓存
/// </summary>
public async Task InvalidateAsync(string key)
{
_memoryCache.Remove(key);
await _distributedCache.RemoveAsync(key);
}
/// <summary>
/// 模式匹配批量失效
/// </summary>
public async Task InvalidateByPatternAsync(string pattern)
{
// 如果使用 Redis,可以通过 KEYS 或 SCAN 命令匹配
_logger.LogWarning("批量失效缓存:{Pattern}", pattern);
}
}内存缓存配置
// Program.cs — 内存缓存配置
builder.Services.AddMemoryCache(options =>
{
options.SizeLimit = 10000; // 设置缓存条目总数上限
});
// 使用压缩属性的缓存
builder.Services.AddSingleton(sp =>
{
var cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 10000,
CompactionPercentage = 0.25, // 内存压力时压缩 25%
ExpirationScanFrequency = TimeSpan.FromMinutes(5) // 扫描过期项的频率
});
return cache;
});
/// <summary>
/// 缓存使用示例
/// </summary>
public class ProductCacheService
{
private readonly IMemoryCache _cache;
public ProductCacheService(IMemoryCache cache)
{
_cache = cache;
}
public async Task<Product> GetProductAsync(int id)
{
var key = $"product:{id}";
return await _cache.GetOrCreateAsync(key, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
entry.SlidingExpiration = TimeSpan.FromMinutes(2); // 2分钟内没有访问就过期
entry.SetSize(1); // 每个条目占1个大小单位
entry.Priority = CacheItemPriority.Normal; // 可选:Low, Normal, High, NeverRemove
// 注册后回调 — 缓存被移除时触发
entry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
// reason: Expired, Evicted, TokenExpired, Removed, Replaced, Capacity
}
});
return _repository.GetProductAsync(id);
});
}
}数据库查询优化
EF Core 查询优化
/// <summary>
/// EF Core 查询优化实战
/// </summary>
public class OptimizedQueryService
{
private readonly AppDbContext _context;
// ===== 1. AsNoTracking — 只读查询不加追踪 =====
// 性能提升30%以上,减少内存占用
public async Task<List<User>> GetUsersReadOnly()
{
return await _context.Users
.AsNoTracking()
.Where(u => u.IsActive)
.ToListAsync();
}
// ===== 2. Select 投影 — 只查询需要的列 =====
public async Task<List<UserDto>> GetUserList()
{
return await _context.Users
.AsNoTracking()
.Where(u => u.IsActive)
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email
// 不查询 PasswordHash、Avatar 等大字段
})
.ToListAsync();
}
// ===== 3. 编译查询 — 高频查询优化 =====
// 跳过每次查询的 LINQ 表达式解析开销
private static readonly Func<AppDbContext, int, Task<User>> _compiledGetUser =
EF.CompileAsyncQuery((AppDbContext context, int id) =>
context.Users.FirstOrDefault(u => u.Id == id));
private static readonly Func<AppDbContext, string, Task<List<User>>> _compiledGetByName =
EF.CompileAsyncQuery((AppDbContext context, string name) =>
context.Users.Where(u => u.Name.Contains(name)).ToList());
public Task<User> GetByIdCompiled(int id) => _compiledGetUser(_context, id);
// ===== 4. 分页查询 =====
public async Task<PagedResult<User>> GetPagedAsync(int page, int pageSize)
{
var query = _context.Users.AsNoTracking().Where(u => u.IsActive);
var total = await query.CountAsync();
var items = await query
.OrderBy(u => u.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return new PagedResult<User>(items, total, page, pageSize);
}
// ===== 5. 避免 N+1 查询 =====
// 错误:循环中查询(N+1问题)
public async Task<List<OrderDto>> GetOrdersWithUsers_Bad()
{
var orders = await _context.Orders.ToListAsync();
foreach (var order in orders)
{
// 每个订单都查一次用户 — N+1!
order.User = await _context.Users.FindAsync(order.UserId);
}
return MapToDto(orders);
}
// 正确:使用 Include 预加载
public async Task<List<OrderDto>> GetOrdersWithUsers_Good()
{
return await _context.Orders
.AsNoTracking()
.Include(o => o.User)
.Where(o => o.Status == OrderStatus.Active)
.Select(o => new OrderDto
{
Id = o.Id,
UserName = o.User.Name,
TotalAmount = o.TotalAmount
})
.ToListAsync();
}
// ===== 6. 批量操作 =====
public async Task BulkUpdateAsync(List<User> users)
{
// 使用 UpdateRange 而不是循环 Update + SaveChanges
_context.Users.UpdateRange(users);
await _context.SaveChangesAsync();
}
// ===== 7. 原始 SQL — 复杂查询优化 =====
public async Task<List<SalesReport>> GetSalesReport(DateTime start, DateTime end)
{
return await _context.SalesReports
.FromSqlRaw(@"
SELECT Date, SUM(Amount) as TotalAmount, COUNT(*) as OrderCount
FROM Orders
WHERE CreatedAt BETWEEN {0} AND {1}
GROUP BY Date
ORDER BY Date", start, end)
.AsNoTracking()
.ToListAsync();
}
private List<OrderDto> MapToDto(List<Order> orders) => new();
}
public record PagedResult<T>(List<T> Items, int Total, int Page, int PageSize);
public class UserDto { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } }
public class OrderDto { public int Id { get; set; } public string UserName { get; set; } public decimal TotalAmount { get; set; } }
public class SalesReport { public DateTime Date { get; set; } public decimal TotalAmount { get; set; } public int OrderCount { get; set; } }响应压缩
配置响应压缩中间件
/// <summary>
/// 响应压缩配置
/// Brotli 压缩率最好,Gzip 兼容性最好
/// </summary>
// Program.cs
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true; // HTTPS 也启用压缩
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
"application/json",
"text/json",
"text/csv",
"application/xml"
});
});
// Brotli 压缩配置
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal; // 最高压缩率
});
// Gzip 压缩配置
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest; // 最快压缩速度
});
// 注册中间件(要在 UseRouting 之前)
app.UseResponseCompression();压缩效果对比
| 压缩方式 | 压缩率 | CPU 开销 | 浏览器支持 |
|---|---|---|---|
| 不压缩 | 0% | 无 | 所有 |
| Gzip | ~70% | 低 | 所有 |
| Brotli | ~80% | 中 | 现代浏览器 |
BenchmarkDotNet 基准测试
基础基准测试
/// <summary>
/// BenchmarkDotNet — .NET 官方基准测试框架
/// Install-Package BenchmarkDotNet
/// 必须在 Release 模式下运行
/// </summary>
[MemoryDiagnoser] // 显示内存分配情况
[RankColumn] // 显示排名
public class StringOperationsBenchmark
{
private readonly string[] _items =
Enumerable.Range(0, 100).Select(i => $"item{i}").ToArray();
[Benchmark(Baseline = true, Description = "字符串拼接")]
public string StringConcat()
{
var result = "";
foreach (var item in _items)
result += item;
return result;
}
[Benchmark(Description = "StringBuilder")]
public string UseStringBuilder()
{
var sb = new StringBuilder();
foreach (var item in _items)
sb.Append(item);
return sb.ToString();
}
[Benchmark(Description = "String.Join")]
public string UseStringJoin()
{
return string.Join("", _items);
}
[Benchmark(Description = "string.Concat")]
public string UseStringConcat()
{
return string.Concat(_items);
}
}
// 运行基准测试
public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run<StringOperationsBenchmark>();
}
}JSON 序列化基准测试
/// <summary>
/// JSON 序列化性能对比
/// </summary>
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class JsonSerializationBenchmark
{
private List<UserData> _data;
private string _json;
[GlobalSetup]
public void Setup()
{
_data = Enumerable.Range(0, 1000)
.Select(i => new UserData { Id = i, Name = $"User{i}", Email = $"user{i}@test.com" })
.ToList();
_json = JsonSerializer.Serialize(_data);
}
[Benchmark(Baseline = true, Description = "System.Text.Json")]
public string Serialize_SystemTextJson()
{
return JsonSerializer.Serialize(_data);
}
[Benchmark(Description = "Newtonsoft.Json")]
public string Serialize_Newtonsoft()
{
return JsonConvert.SerializeObject(_data);
}
[Benchmark(Description = "ST.Json SourceGen")]
public string Serialize_SourceGen()
{
return JsonSerializer.Serialize(_data, UserDataContext.Default.ListUserData);
}
}
[JsonSerializable(typeof(List<UserData>))]
public partial class UserDataContext : JsonSerializerContext { }
public class UserData
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}运行和解读结果
# 运行基准测试(必须在 Release 模式)
dotnet run -c Release
# 输出示例:
# | Method | Mean | Error | StdDev | Rank | Allocated |
# |--------------------- |----------:|---------:|---------:|-----:|----------:|
# | string.Concat | 12.34 us | 0.245 us | 0.229 us | 1 | 960 B |
# | String.Join | 15.67 us | 0.312 us | 0.292 us | 2 | 1,024 B |
# | StringBuilder | 18.90 us | 0.378 us | 0.354 us | 3 | 2,048 B |
# | 字符串拼接(Baseline) | 890.12 us | 17.23 us | 16.12 us | 4 | 260,000 B |优化清单
| 优化方向 | 具体措施 | 预期收益 | 适用场景 |
|---|---|---|---|
| 异步编程 | async/await、Task.WhenAll 并行 | 吞吐量提升50%+ | I/O 密集型 |
| 内存优化 | Span/Memory、ArrayPool、ObjectPool | GC压力减少50%+ | 高频处理 |
| GC 调优 | ServerGC、HeapCount 配置 | 暂停时间减少 | 服务端应用 |
| 缓存策略 | 多级缓存、滑动过期 | 响应时间减少80%+ | 读多写少 |
| 数据库优化 | AsNoTracking、编译查询、索引 | 查询速度提升10x+ | 数据密集型 |
| 响应压缩 | Brotli/Gzip 压缩 | 传输体积减少70%+ | API 响应 |
| 基准测试 | BenchmarkDotNet 量化对比 | 精确数据驱动优化 | 全场景 |
优点
缺点
总结
性能优化的核心原则是"先测量,再优化"。使用 dotnet-trace/dotnet-counters 定位瓶颈,用 BenchmarkDotNet 建立基准和验证效果。从异步编程(Task.WhenAll 并行、ConfigureAwait)、内存优化(Span/Memory、ArrayPool、对象池)、GC 调优(ServerGC 配置)、缓存策略(多级缓存)、数据库优化(AsNoTracking、编译查询、投影)和响应压缩六个方向系统优化,可以显著提升 .NET 应用的性能表现。记住:可读性优先,优化聚焦关键路径,用数据驱动决策。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《.NET性能优化实战》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《.NET性能优化实战》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《.NET性能优化实战》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《.NET性能优化实战》最大的收益和代价分别是什么?
