攻克十大 .NET 性能反模式,提升应用效能与可扩展性

作者:微信公众号:【架构师老卢】
2-4 17:36
24

性能瓶颈会降低应用程序的效率、可扩展性和用户体验。许多 .NET 开发者在不知不觉中陷入了一些反模式,随着时间的推移,这些反模式会降低应用程序的性能。在本文中,我们将探讨十大 .NET 性能反模式,解释它们为何会产生问题,并展示如何使用优化后的解决方案来修复它们。

1. 过度的对象分配和垃圾回收压力

问题

创建过多的短期对象会导致频繁的垃圾回收(GC)周期,从而影响应用程序的性能。

修复方法

  • 对可重用对象使用对象池。
  • 对于小型的不可变对象,优先使用结构体而非类。
  • 通过使用 SpanMemory 减少分配。
  • 必要时使用 GCSettings.LargeObjectHeapCompactionMode 进行垃圾回收调优。

示例

// 不要这样做:
var data = new byte[1024];
// 使用 MemoryPool 来重用已分配的内存:
var pool = MemoryPool<byte>.Shared;
using (var owner = pool.Rent(1024))
{
    var memory = owner.Memory;
    // 在这里处理内存
}

2. 阻塞异步代码(同步调用异步方法)

问题

在异步方法上调用 .Result.GetAwaiter().GetResult() 会阻塞线程,并且可能导致死锁。

修复方法

  • 始终使用 async/await
  • 避免混合使用同步和异步代码。

示例

// 反模式
public string GetData()
{
    return GetDataAsync().Result; // 阻塞线程
}

// 修复
public async Task<string> GetDataAsync()
{
    return await FetchDataFromServiceAsync();
}

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 valueOptimized = propertyDelegate(instance);

5. 在循环中使用字符串拼接

问题

字符串是不可变的,重复拼接会在内存中创建多个新的字符串对象。

修复方法

对于重复的字符串操作,使用 StringBuilder

示例

// 反模式
string result = "";
for (int i = 0; i < 1000; i++)
{
    result += i.ToString();
}

// 修复
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i);
}
string optimizedResult = sb.ToString();

6. 忽略对昂贵计算的缓存

问题

多次执行相同的昂贵计算会浪费资源。

修复方法

  • 使用 MemoryCache、Redis 或 Lazy 进行缓存。
  • 对重复的响应实现输出缓存。

示例

private static readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());

public string GetExpensiveData(string key)
{
    if (!_cache.TryGetValue(key, out string cachedData))
    {
        cachedData = ComputeExpensiveData();
        _cache.Set(key, cachedData, TimeSpan.FromMinutes(10));
    }
    return cachedData;
}

7. 不使用异步数据库调用

问题

同步数据库查询会阻塞线程,降低可扩展性。

修复方法

使用 EF Core 方法的异步版本,如 ToListAsync()

示例

// 反模式
var users = context.Users.ToList(); // 阻塞线程

// 修复
var users = await context.Users.ToListAsync();

8. 在性能关键路径中过度日志记录

问题

在热点路径中记录过多日志会减慢执行速度。

修复方法

  • 使用条件日志记录。
  • 降低关键部分的日志详细程度。

示例

// 反模式
_logger.LogInformation("Processing item: {Id}", item.Id);

// 修复
if (_logger.IsEnabled(LogLevel.Debug))
{
    _logger.LogDebug("Processing item: {Id}", item.Id);
}

9. 低效地使用 LINQ

问题

某些 LINQ 操作,如在过滤之前调用 ToList(),会导致不必要的内存使用。

修复方法

通过避免过早调用 .ToList() 来使用延迟执行。

示例

// 反模式
var filteredUsers = context.Users.ToList().Where(u => u.IsActive);

// 修复
var filteredUsers = context.Users.Where(u => u.IsActive).ToList();

10. 忽略使用异步流处理大数据

问题

一次性将大型数据集加载到内存中会导致高内存消耗。

修复方法

使用 IAsyncEnumerable 来流式传输数据。

示例

public async IAsyncEnumerable<User> GetUsersAsync()
{
    await foreach (var user in context.Users.AsAsyncEnumerable())
    {
        yield return user;
    }
}

关键要点

  • 避免过度的内存分配,以减少垃圾回收开销。
  • 采用异步编程,提高响应能力。
  • 优化数据库查询,防止不必要的数据检索。
  • 使用缓存,最小化冗余计算。
  • 减少对性能敏感路径的日志记录。

修复这些反模式将显著提升你的 .NET 应用程序的性能,带来更好的可扩展性和效率。

下一步计划

首先使用 dotnet-traceBenchmarkDotNet 对你的代码进行性能分析,以检测性能瓶颈,然后逐步应用这些修复方法。

相关留言评论
昵称:
邮箱:
阅读排行