案例研究:隔离性能问题(C#、Visual Basic、F#)

使用分析工具调查性能问题并隔离问题区域。 本案例研究使用性能问题的示例应用程序来演示如何使用分析工具提高效率。 如果要比较分析工具,请参阅 应选择哪种工具?

此案例研究涵盖以下主题:

  • 如何使用 Visual Studio 分析工具分析应用程序性能。
  • 如何解释这些工具提供的数据,以确定性能瓶颈。
  • 如何应用实际策略来优化代码,侧重于 .NET 计数器、调用计数和计时数据。

请逐步跟随,然后将这些技术应用到您的应用程序中,使其更高效且节约成本。

隔离性能问题案例研究

本案例研究中的示例应用程序是一个 ASP.NET 应用,它针对模拟数据库运行查询。 该示例基于 诊断示例

示例应用程序的主要性能问题在于低效的编码模式。 应用程序的性能瓶颈严重影响了其效率。 此问题包括以下症状:

  • CPU 使用率低:应用程序显示 CPU 使用率低,表明 CPU 不是瓶颈。

  • 高线程池线程计数:线程计数相对较高且稳步上升,这表明线程池不足。

  • 应用程序响应速度缓慢:由于缺少可用于处理新工作项的线程,应用程序响应速度缓慢。

案例研究旨在通过利用 Visual Studio 的分析工具分析应用程序的性能来解决这些问题。 通过了解应用程序性能的改进位置和方式,开发人员可以实现优化,使代码更快、更高效。 最终目标是提高应用程序的整体性能,使其更高效且经济高效地运行。

挑战

解决示例 .NET 应用程序中的性能问题提出了一些挑战。 这些挑战源于诊断性能瓶颈的复杂性。 修复所描述问题的主要挑战如下:

  • 诊断性能瓶颈:主要挑战之一是准确识别性能问题的根本原因。 低 CPU 使用率与性能缓慢相结合可能会产生多种因素。 开发人员必须有效地使用分析工具来诊断这些问题,这需要一定的理解这些工具的工作原理以及如何解析其输出。

  • 知识和资源限制:团队可能面临与知识、专长和资源相关的限制。 分析和优化应用程序需要特定的技能和经验,并非所有团队都可以立即访问这些资源。

应对这些挑战需要一种战略方法,该方法结合了分析工具、技术知识以及精心规划和测试的有效使用。 案例研究旨在指导开发人员完成此过程,提供策略和见解,以克服这些挑战并提高应用程序的性能。

策略

下面是本案例研究中方法的高级视图:

  • 我们在收集性能数据时通过监视 .NET 计数器指标开始调查。 与 CPU 使用率工具一样,Visual Studio .NET 计数器 工具也是性能调查的一个很好的起点。
  • 接下来,为了获得更多见解以帮助识别并隔离问题或提高性能,请考虑使用其他分析工具之一收集跟踪记录。 例如,使用 Instrumentation 工具查看调用计数和计时数据。

数据收集需要以下任务:

  • 将应用设置为发布版本。
  • 从性能探查器中选择 .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 相对较高。

.NET 计数器工具中显示的计数器的屏幕截图。

CPU 使用率较低而线程计数稳步上升,可能预示着线程池资源匮乏。 线程池被迫不断创建新的线程。 当池没有可用于处理新工作项的线程,并且通常会导致应用程序响应缓慢时,会发生线程池不足。

鉴于 CPU 使用率较低和线程计数相对较高,并根据线程池可能发生资源枯竭的理论,请改用检测工具。

调查呼叫计数和计时数据

让我们查看使用检测工具进行的跟踪,看看是否能够更深入地了解线程正在发生的情况。

使用检测工具收集跟踪并将其加载到 Visual Studio 后,我们首先检查显示汇总数据的初始 .diagsession 报表页。 在收集的跟踪中,我们使用报表中的“打开详细信息”链接,然后选择“火焰图”

在检测工具中的 检测工具中的火焰图的屏幕截图。

火焰图可视化效果显示,QueryCustomerDB 函数(以黄色显示)负责应用的运行时间的很大一部分。

右键单击 QueryCustomerDB 函数,然后在调用树 中选择视图。

检测工具中的调用树的屏幕截图。

应用中 CPU 使用率最高的代码路径称为 热路径。 热路径火焰图标(显示“热路径”图标的屏幕截图。)可帮助快速识别可能改进的性能问题。

调用树 视图中,可以看到热路径包括 QueryCustomerDB 函数,这指向潜在的性能问题。

相对于在其他函数中花费的时间,QueryCustomerDB 函数的 自身平均自身 值非常高。 与 TotalAvg Total不同,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 性能工具。