性能瓶颈会严重影响应用的效率、可扩展性和用户体验。很多.NET开发者不知不觉地陷入了这些会慢慢降低性能的陷阱。在这篇文章中,我们将探讨前十大.NET性能反模式,解释为什么它们有问题,并分享如何用更好的方法来解决这些问题。
1. 过多的对象分配,以及由此带来的垃圾收集压力。 问题:创建太多短暂存活的对象会导致频繁的GC(垃圾回收)操作,影响应用程序的性能。
执行修复:- 对可重用对象使用对象池。
- 对这样的小型、不可变对象,使用结构体而非类。
- 通过使用Span和Memory减少分配次数。
- 如有需要,使用
GCSettings.LargeObjectHeapCompactionMode
启用垃圾回收调优。
例子:
// 还是这样做更好:
var data = new byte[1024]; // 使用 MemoryPool 重用分配的内存:
var pool = MemoryPool<byte>.Shared;
using (var 租借器 = pool.Rent(1024))
{
var memory = 租借器.Memory;
// 在这里处理内存
}
阻塞异步代码(将同步代码应用到异步代码上)
存在的问题在异步方法中调用 .Result
或 .GetAwaiter().GetResult()
会阻塞线程,这可能导致死锁。
- 始终使用 async/await 语法。
- 避免将同步代码和异步代码混用。
来个例子,比如:
// 反模式示例
public string 获取数据()
{
return 获取数据Async().Result; // 阻塞了当前线程
}
// 正确的实现
public async Task<string> 获取数据Async()
{
return await 获取服务数据Async();
}
3. 效率低下的数据库查询操作
问题来了:
- 在类似 Entity Framework 的 ORM 中遇到的 N+1 查询问题。
- 没有使用合适的索引策略。
- 查询的数据量过大。
- 避免使用懒加载,转而使用急加载,提前包含所有必需的相关数据。
- 通过分页和索引优化这些查询。
- 使用EF Core 日志来监控查询。
// 不好的模式
var orders = context.Orders.ToList();
foreach (var order in orders)
{
var customer = context.Customers.Find(order.CustomerId); // N+1 的问题
}
// 修复方案
var ordersWithCustomers = context.Orders.Include(o => o.Customer).ToList();
4. 过度反射
问题:
反射机制因元数据检查导致明显的性能损失。
修复:- 使用编译表达式或源代码生成器作为替代。
- 将反射结果缓存起来,而不是反复调用。
// 反模式代码
var type = typeof(MyClass);
var property = type.GetProperty("MyProperty");
var value = property.GetValue(instance);
// 改进
var propertyDelegate = (Func<MyClass, object>)Delegate.CreateDelegate(
typeof(Func<MyClass, object>), null, type.GetProperty("MyProperty").GetMethod);
var 优化后的值 = propertyDelegate(instance);
5. 在循环中使用字符串拼接技术
遇到的问题:
这些字符串是不可变的;频繁拼接会在内存中频繁创建新的字符串对象。
修复:- 使用 StringBuilder 来处理频繁修改的字符串。
// 反模式示例
string 结果 = "";
for (int i = 0; i < 1000; i++)
{
结果 += i.ToString();
}
// 改进
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Append(i);
}
string 优化后的结果 = sb.ToString();
6. 忽略昂贵计算缓存(忽略昂贵计算的缓存处理)
问题:
重复高成本的计算会浪费资源,这样做。
要解决的问题:- 使用 MemoryCache 、 Redis 或 Lazy 缓存数据。
- 实现 响应缓存 功能以处理重复的响应。
private static readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
public string 获取昂贵的数据(string key)
{
if (!_cache.TryGetValue(key, out string 缓存内容))
{
缓存内容 = 计算昂贵的数据();
_cache.Set(key, 缓存内容, TimeSpan.FromMinutes(10));
}
return 缓存内容;
}
7. 避免使用 async
数据库调用
问题如下:
数据库查询同步会阻塞了线程并影响可扩展性。
《修復:》- 使用像
ToListAsync()
这样的async
版本的 EF Core 方法。
例如,这是一个例子:
// 反模式代码
var users = context.Users.ToList(); // 阻塞当前线程
// 修复方法
var users = await context.Users.ToListAsync();
8. 性能关键路径中的过度日志记录
出问题了:
在热点路径中记录过多会拖慢运行速度。
需要解决的:- 使用条件日志。
- 在关键部分减少日志记录的详细信息。
这是一个空的例子。
// 不良模式
_logger.LogInformation("处理项目:{ Id }", item.Id);
// 改进
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("处理项目:{ Id }", item.Id);
}
第9章 不恰当地使用LINQ (语言集成查询)
问题:在进行 LINQ 操作时,在调用 ToList()
后进行筛选,会造成不必要的内存使用。
- 避免过早使用
.ToList()
以实现延迟执行。
例如:
// 反模式
var filteredUsers = context.Users.ToList().Where(u => u.IsActive);
// 修正
context.Users.Where(u => u.IsActive).ToList();
10. 忽略处理大规模数据时的异步流
忽略处理大规模数据时的异步流。
问题如下:一次性加载大型数据集到内存中会占用大量内存。
修复:- 使用 IAsyncEnumerable 流式传输数据。
public async IAsyncEnumerable<User> 获取用户()
{
foreach (var user in context.Users.AsAsyncEnumerable())
{
yield return user;
}
}
要点:
- 避免分配过多内存以减少垃圾回收的负担。
- 使用异步编程以提高响应性。
- 优化数据库查询以减少不必要的数据检索。
- 使用缓存以减少重复计算量。
- 在性能关键路径上减少日志记录。
修复这些反模式问题,将显著提高你的.NET应用程序的性能,从而让你的应用更加可扩展和高效。
下一步是什么?首先,使用 dotnet-trace 或 BenchmarkDotNet 对您的代码进行性能分析,找出性能瓶颈,然后逐步应用这些修复措施。
🚀 加油!编程愉快!
點擊查看更多內容
為 TA 點贊
評論
評論
共同學習,寫下你的評論
評論加載中...
作者其他優質文章
正在加載中
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦