案例研究:优化代码和降低计算成本的初学者指南(C#、Visual Basic、C++、F#)

优化代码可降低计算时间和成本。 此案例研究演示如何使用 Visual Studio 分析工具来识别和修复示例 .NET 应用程序中的性能问题。 如果要比较分析工具,请参阅 应选择哪种工具?

本指南涵盖:

  • 如何使用 Visual Studio 分析工具分析和提高性能。
  • 优化 CPU 使用率、内存分配和数据库交互的实际策略。

应用这些技术以使自己的应用程序更高效。

优化案例研究

示例 .NET 应用程序使用 Entity Framework 针对博客和文章的 SQLite 数据库运行查询。 它执行许多查询,模拟实际数据检索方案。 该应用基于 Entity Framework 入门示例,但使用更大的数据集。

关键性能问题包括:

  • CPU 使用率高:计算或处理任务效率低下会增加 CPU 消耗和成本。
  • 低效的内存分配:内存管理不佳导致垃圾回收过多,性能降低。
  • 数据库开销:低效的查询和过多的数据库调用会降低性能。

本案例研究使用 Visual Studio 分析工具来查明和解决这些问题,目的是使应用程序更高效且经济高效。

挑战

解决这些性能问题涉及以下几个难题:

  • 诊断瓶颈:识别 CPU、内存或数据库开销过高的根本原因需要有效地使用分析工具和正确解释结果。
  • 知识和资源约束:分析和优化需要特定的技能和经验,这可能并不总是可用的。

将分析工具、技术知识和仔细测试相结合的战略方法对于克服这些挑战至关重要。

策略

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

数据收集需要以下任务:

  • 将应用设置为“Release 版本”。
  • 在性能探查器中选择 CPU 使用率工具(Alt+F2)。
  • 在性能分析器中,启动应用并收集追踪数据。

检查 CPU 使用率较高的区域

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

在 CPU 使用率工具中打开详细信息的屏幕截图。

在报告详细信息视图中,打开“调用树”视图。 应用中 CPU 使用率最高的代码路径称为 热路径。 热路径火焰图标(显示“热路径”图标的屏幕截图。)可帮助快速识别可能改进的性能问题。

在“调用树”视图中,您可以看到应用中 GetBlogTitleX 方法的 CPU 使用率较高,大约占用了应用 CPU 使用率的 60%。 但是,GetBlogTitleX 的“自 CPU”值低,仅为 0.10% 左右。 与 总 CPU不同,自身 CPU 值排除了其他函数消耗的时间,因此我们知道要更深入地查看调用树中的实际瓶颈。

CPU 使用率工具中“调用树”视图的屏幕截图。

GetBlogTitleX 对两个 LINQ DLL 进行外部调用,这两个 LINQ DLL 使用了大部分 CPU 时间,非常高的“自 CPU”值就证明了这一点。 这是 LINQ 查询可能是要优化的区域的第一个线索。

CPU 使用率工具中“调用树”视图的屏幕截图,其中突出显示了自身 CPU。

若要获取可视化的调用树和数据的不同视图,请打开 火焰图 视图。 (或者,右键单击 GetBlogTitleX 并选择“在火焰图中查看”。)同样,GetBlogTitleX 方法似乎对应用的大量 CPU 使用率负责(黄色所示)。 对 LINQ DLLs 的外部调用显示在 GetBlogTitleX 框下,它们正在占用该方法的所有 CPU 时间。

CPU 使用情况工具中火焰图视图的屏幕截图。

收集其他数据

通常,其他工具可以提供其他信息来帮助分析和隔离问题。 在本案例研究中,我们采用以下方法:

  • 首先,查看内存使用情况。 高 CPU 使用率和高内存使用率之间可能存在相关性,因此查看这两者来隔离问题会很有帮助。
  • 由于我们标识了 LINQ DLL,因此我们还将查看数据库工具。

检查内存使用情况

若要查看应用在内存使用情况方面的进展,我们使用 .NET 对象分配工具(对于C++,可以改用内存使用情况工具)收集跟踪。 内存跟踪中的 调用树 视图显示热路径,并帮助我们确定内存使用率较高的区域。 毫不奇怪,GetBlogTitleX 方法似乎正在生成大量对象! 事实上,超过 900,000 个对象分配。

.NET 对象分配工具中调用树视图的屏幕截图。

创建的大多数对象都是字符串、对象数组和 Int32。 我们也许可以通过检查源代码来了解这些类型是如何生成的。

在数据库工具中查看查询

在性能探查器中,我们选择数据库工具而不是 CPU 使用率(或者,选择两者)。 收集到跟踪后,请在诊断页面中打开“查询”选项卡。 在数据库跟踪的“查询”选项卡中,可以看到第一行显示最长的查询 2446 毫秒。 记录 列显示查询读取的记录数。 可以使用此信息进行以后的比较。

数据库工具中数据库查询的屏幕截图。

通过检查查询列中 LINQ 生成的 SELECT 语句,我们将第一行标识为与 GetBlogTitleX 方法关联的查询。 若要查看完整的查询字符串,请展开列宽。 完整的查询字符串为:

SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"

请注意,应用在这里检索大量列值,也许比我们需要的更多。 让我们看看源代码。

优化代码

是时候看看 GetBlogTitleX 源代码了。 在“数据库”工具中,右键单击查询并选择“转到源文件”。 在 GetBlogTitleX的源代码中,我们发现以下代码使用 LINQ 读取数据库。

foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
  {
    foreach (var post in blog.Posts)
    {
      if (post.Author == "Fred Smith")
      {
        Console.WriteLine($"Post: {post.Title}");
      }
  }
}

此代码使用 foreach 循环在数据库中搜索任何以“Fred Smith”作为作者的博客。 查看它时,可以看到大量对象在内存中生成:数据库中每个博客的新对象数组、每个 URL 的关联字符串以及文章中包含的属性的值,例如博客 ID。

我们稍做一些研究,并找到有关如何优化 LINQ 查询的一些常见建议。 或者,我们可以节省时间,让 科皮洛特 为我们做研究。

如果使用 Copilot,请从上下文菜单中选择 询问 Copilot,然后键入以下问题:

Can you make the LINQ query in this method faster?

提示

可以使用斜杠命令(如 /optimize)来帮助 Copilot 形成好的问题。

在此示例中,Copilot 提供以下建议的代码更改以及说明。

public void GetBlogTitleX()
{
    var posts = db.Posts
        .Where(post => post.Author == "Fred Smith")
        .Select(post => post.Title)
        .ToList();

    foreach (var postTitle in posts)
    {
        Console.WriteLine($"Post: {postTitle}");
    }
}

此代码包括多个更改,以帮助优化查询:

  • 添加了 Where 子句,并消除了其中一个 foreach 循环。
  • 仅投影 Select 语句中的 Title 属性,这就是我们在本示例中所需的全部内容。

接下来,我们将使用分析工具重新测试。

结果

更新代码后,我们重新运行 CPU 使用情况工具以收集跟踪。 调用树视图显示 GetBlogTitleX 仅运行了 1754 毫秒,占用应用的 CPU 总数的 37%,比 59% 有了显著改善。

CPU 使用率工具的“调用树”视图中改进的 CPU 使用率的屏幕截图。

切换到 火焰图 视图以查看显示改进效果的另一个可视化效果。 在此视图中,GetBlogTitleX 还使用较小的 CPU 部分。

CPU 使用率工具的“火焰图”视图中改进的 CPU 使用率的屏幕截图。

在数据库工具跟踪中检查结果,使用此查询仅读取了两条记录,而不是十万条! 此外,查询得到了大大简化,并消除了之前生成的不必要的 LEFT JOIN。

数据库工具中更快查询时间的屏幕截图。

接下来,我们在 .NET 对象分配工具中重新检查结果,并看到 GetBlogTitleX 只负责 56,000 个对象分配,比 900,000 个减少了近 95%%!

.NET 对象分配工具中内存分配减少的屏幕截图。

迭代

可能需要进行多次优化,我们可以继续通过代码更改进行迭代,以查看哪些更改能够提高性能并帮助降低计算成本。

后续步骤

以下文章和博客文章提供了详细信息,可帮助你了解如何有效地使用 Visual Studio 性能工具。