本案例研究演示如何使用 Visual Studio 分析工具识别和解决示例 ASP.NET 应用程序中的性能问题。 有关分析工具的比较,请参阅 应选择哪种工具?
学习内容:
- 如何使用 Visual Studio 分析工具分析应用程序性能。
- 如何解读分析数据以查找瓶颈。
- 使用 .NET 计数器、调用计数和计时数据优化代码的实际策略。
应用这些技术来改进自己的应用程序。
隔离性能问题案例研究
示例 ASP.NET 应用针对模拟数据库运行查询,并基于 诊断示例。
关键性能症状:
- CPU 使用率低:CPU 不是瓶颈。
- 高线程池线程计数:线程计数稳步上升,表示线程池饥饿。
- 应用程序响应缓慢:由于缺少可用线程,应用响应速度缓慢。
此案例研究使用 Visual Studio 分析工具来查明和解决这些问题,帮助你使代码更快、更高效。
挑战
解决这些问题涉及以下几个难题:
- 诊断瓶颈:CPU 使用率低,性能缓慢可能会导致多种原因。 有效使用分析工具并解释其输出至关重要。
- 知识和资源约束:分析和优化需要特定的技能和经验,这可能并不总是可用的。
将分析工具、技术知识和仔细测试相结合的战略方法是克服这些挑战的关键。
策略
下面是本案例研究中方法的高级视图:
- 首先监视 .NET 计数器指标,同时收集性能数据。 Visual Studio 的 .NET 计数器 工具是一个很好的起点。
- 为了获得更深入的见解,可以使用附加的分析工具(例如用于调用计数和计时数据的检测工具)来收集跟踪信息。
数据收集需要以下任务:
- 将应用设置为“Release 版本”。
- 在性能探查器中选择 .NET 计数器工具(Alt+F2)。
- 启动应用并收集跟踪记录。
检查性能计数器
运行应用时,我们会在 .NET 计数器工具中观察计数器。 对于初始调查,要关注的一些关键指标包括:
-
CPU Usage
。 请观察此计数器,以确认性能问题是在 CPU 使用率高时还是低时发生。 这可以是特定类型的性能问题的线索。 例如:- CPU 使用率较高时,请使用 CPU 使用率工具确定我们也许能够优化代码的区域。 有关此教程,请参阅 案例研究:优化代码初学者指南。
- 如果 CPU 使用率低,可使用检测工具,根据壁挂时钟时间识别调用计数和平均函数时间。 这可能有助于识别争用或线程池不足等问题。
-
Allocation Rate
。 对于提供请求的 Web 应用,速率应相当稳定。 -
GC Heap Size
。 请观看此计数器,了解内存使用率是否持续增长并可能泄漏。 如果它看起来很高,请使用其中一个内存使用工具。 -
Threadpool Thread Count
。 对于提供请求的 Web 应用,请观看此计数器,查看线程计数是否保持稳定或以稳定速率上升。
以下示例显示了 CPU Usage
的值较低,而 ThreadPool Thread Count
相对较高。
如果 CPU 使用率低,但线程计数稳定上升,可能说明线程池饥饿。 线程池被迫不断创建新的线程。 当池没有可用于处理新工作项的线程,并且通常会导致应用程序响应缓慢时,会发生线程池不足。
如果 CPU 使用率低但线程计数相对较高,并根据可能存在线程池饥饿问题的原理,这时可切换到使用检测工具。
调查呼叫计数和计时数据
让我们看一下检测工具中的跟踪,看看是否能够更深入了解线程发生了什么。
使用检测工具收集跟踪并将其加载到 Visual Studio 后,我们首先检查显示汇总数据的初始 .diagsession 报告页。 在收集的跟踪中,我们使用报告中的打开详细信息链接,然后选择火焰图。
在检测工具中的
火焰图可视化效果显示,QueryCustomerDB
函数(以黄色显示)负责应用的运行时间的很大一部分。
右键单击 QueryCustomerDB
函数,然后在调用树 中选择视图。
应用中 CPU 使用率最高的代码路径称为 热路径。 热路径火焰图标()可帮助快速识别可能改进的性能问题。
在 调用树 视图中,可以看到热路径包括 QueryCustomerDB
函数,这指向潜在的性能问题。
相对于在其他函数中花费的时间, 函数的 自身 和 QueryCustomerDB
值非常高。 与 Total 和 Avg Total不同,Self 值排除在其他函数中花费的时间,因此这是查找性能瓶颈的好位置。
提示
如果 Self 值并不高,相对较低,则可能需要调查 QueryCustomerDB
函数调用的实际查询。
双击 QueryCustomerDB
函数以显示函数的源代码。
public ActionResult<string> QueryCustomerDB()
{
Customer c = QueryCustomerFromDbAsync("Dana").Result;
return "success:taskwait";
}
我们做了一些研究。 或者,我们可以节省时间,让 科皮洛特 为我们做研究。
如果我们使用 Copilot,我们从上下文菜单中选择询问 Copilot,然后键入以下问题:
Can you identify a performance issue in the QueryCustomerDB method?
提示
可以使用斜杠命令(如 /optimize)来帮助 Copilot 形成好的问题。
Copilot 告诉我们,此代码在不使用 await 的情况下调用异步 API。 这属于 sync-over-async 代码模式,是导致线程池饥饿的常见原因,并且可能会阻止线程。
要解决此问题,请使用 await。 在此示例中,Copilot 提供以下代码建议以及说明。
public async Task<ActionResult<string>> QueryCustomerDB()
{
Customer c = await QueryCustomerFromDbAsync("Dana");
return "success:taskwait";
}
如果看到与数据库查询相关的性能问题,可以使用 数据库工具 调查某些调用是否较慢。 此数据可能表示有机会优化查询。 有关如何使用数据库工具调查性能问题的教程,请参阅 案例研究:优化代码初学者指南。 数据库工具支持将 .NET Core 与 ADO.NET 或 Entity Framework Core 配合使用。
若要在 Visual Studio 中获取单个线程行为的可视化效果,可以在调试时使用 并行堆栈 窗口。 此窗口会显示单个线程以及有关处于等待状态的线程、这些线程正在等待完成的线程以及死锁的信息。
有关线程池饥饿的其他信息,请参阅 检测线程池饥饿。
后续步骤
以下文章和博客文章提供了详细信息,可帮助你了解如何有效地使用 Visual Studio 性能工具。