适用范围:
Visual Studio
Visual Studio for Mac
Visual Studio Code
缩短计算时间意味着降低成本,因此优化代码可以节省资金。 本文还介绍如何使用各种分析工具(包括 CPU 使用率、NET 对象分配和数据库工具)来帮助你完成此任务。CPU 使用率工具可帮助你捕获和可视化应用程序中使用计算资源的位置。 CPU 使用率视图(如调用树和火焰图)提供了一个良好的图形可视化效果,其中显示了应用程序中所用时间的分布情况。 此外,自动见解可能会显示产生很大影响的精确优化。 其他工具可帮助你隔离问题。
若要降低计算成本,请通过跟踪 CPU 使用率开始调查。 CPU 使用率工具通常有助于开始性能调查并优化代码以降低成本。
如果需要其他见解来帮助隔离问题或提高性能,请考虑使用其他分析工具之一收集跟踪。 例如:
-
查看内存使用情况。 对于 .NET,请先尝试 .NET 对象分配工具。 对于 .NET 或 C++,可以查看内存使用情况工具。
-
如果应用使用文件 I/O,请使用文件 I/O 工具。
-
如果使用的是 ADO.NET 或 Entity Framework,可以尝试使用数据库工具来检查 SQL 查询、精确查询时间等。
-
有关比较工具,请参阅
应选择哪种工具?
本文中显示的示例屏幕截图基于 .NET 应用,该应用针对博客和关联的博客文章的数据库运行查询。 首先检查 CPU 使用率跟踪,以寻找优化和降低计算成本的机会。 大致了解所发生的情况后,还将查看其他分析工具的跟踪,以帮助隔离问题。
若要收集本文中显示的数据,请执行以下操作:
-
将应用设置为发布版本
-
从性能探查器 (Alt+F2) 中选择 CPU 使用率工具。 (后面的步骤涉及一些其他工具。)
-
在性能探查器中,启动应用并收集跟踪。
检查高 CPU 使用率的区域
首先使用 CPU 使用率工具收集跟踪。 加载诊断数据时,首先检查显示热门见解和热路径的初始 .diagsession 报表页。 热路径显示应用中 CPU 使用率最高的代码路径。 这些部分可能会提供提示,帮助你快速识别可以改进的性能问题。
还可以在“调用树”视图中查看热路径。 若要打开此视图,请使用报表中的“打开详细信息”链接,然后选择“调用树”。
在此视图中,你将再次看到热路径,其中显示了应用中
GetBlogTitleX
方法的高 CPU 使用率,占应用 CPU 使用率的 60% 左右。 对 LINQ DLL 的两次外部调用使用大部分 CPU 时间。 这是你可能想要查找 LINQ 查询作为要优化的领域的第一个线索。
若要获取可视化的调用树和不同的数据视图,请切换到“火焰图”视图(从与“调用树”相同的列表中选择)。 同样,
GetBlogTitleX
方法似乎对应用的大量 CPU 使用率负责(黄色所示)。 对 LINQ DLL 的外部调用显示在
GetBlogTitleX
框下方,它们使用方法的所有 CPU 时间。
收集其他数据
如果 CPU 使用率工具未提供足够的信息来隔离问题,或者你需要其他见解来帮助提高性能,则可以决定使用其他分析工具之一。 例如,由于我们标识了 LINQ DLL,因此我们将首先尝试数据库工具。 可以多重选择此工具以及 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"
若要查看应用的内存使用情况,请使用 .NET 对象分配工具收集跟踪(对于 C++,请改用内存使用情况工具)。 内存跟踪中的“调用树”视图显示热路径,有助于识别高内存使用率的区域。 毫不奇怪,GetBlogTitleX
方法似乎正在生成大量对象! 事实上,超过 900,000 个对象分配。
创建的大多数对象是字符串、对象数组和 Int32。 可以通过检查源代码来查看这些类型的生成方式。
是时候查看 GetBlogTitleX
源代码了。 在 .NET 对象分配工具中,右键单击方法,然后选择“转到源文件”。 在 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 查询的一些常见建议,并生成此代码。
foreach (var x in db.Posts.Where(p => p.Author.Contains("Fred Smith")).Select(b => b.Title).ToList())
Console.WriteLine("Post: " + x);
在此代码中,你进行了一些更改以帮助优化查询:
添加 Where
子句并消除其中一个 foreach
循环。
仅投影标题属性,该属性是此示例中所需的所有内容。
接下来,使用分析工具重新测试。
更新代码后,重新运行 CPU 使用率工具以收集跟踪。 “调用树”视图显示 GetBlogTitleX
仅运行了 1754 毫秒,占用应用的 CPU 总数的 37%,比 59% 有了显著改善。
切换到“火焰图”视图以查看改进的另一个可视化效果。 在此视图中,GetBlogTitleX
也使用 CPU 的较小部分。
检查数据库工具跟踪中的结果,使用此查询仅读取两条记录,而不是 100,000 条! 此外,查询得到了大大简化,并消除了之前生成的不必要的 LEFT JOIN。
接下来,在 .NET 对象分配工具中重新检查结果,查看 GetBlogTitleX
仅负责 56,000 个对象分配,比 900,000 减少近 95%!
可能需要多次优化,可以继续循环访问代码更改,以查看哪些更改可提高性能并降低计算成本。