65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 性能优化基础

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2013年7月4日

CPOL

15分钟阅读

viewsIcon

36084

Microsoft MVP Tiago Pascoal 介绍了用于优化 Web 应用程序性能的最佳实践、工具和技术。

引言

性能是您应该在开发过程中考虑的问题,而不是仅仅在调试时才考虑,但它常常被视为一种“黑魔法”,凭猜测和必要性驱动。

开发人员经常依赖直觉,并专注于他们认为缓慢的代码段。这里的危险在于,您可能会在只占整体体验 1% 的代码段上获得 100 倍的速度提升(太棒了!),但整体效果并不那么好。或者更糟的是,您的猜测可能完全错误,根本没有问题。

这种优化方式非常缓慢、乏味,而且坦率地说,成本高昂。冒着说出显而易见的事的风险,如果您想避免浪费时间去寻找真正的问题,那么在需要优化时,不要猜测。使用许多可用的工具和分析器来衡量时间或内存的消耗,并将您的时间和精力集中在能够对系统整体性能产生最大影响的改变上。

这是我将在本文中反复强调的一个原则——猜测毫无意义,而且有许多工具可以帮助您精确地定位问题,所以请使用它们!考虑到这一点,我将描述一些您应该使用的最基本的技术和工具,以确保您最大化 ASP.NET 应用程序的性能,让我们开始吧……

高性能应用程序的因素

编写不“懈怠”的代码是所有开发人员都可以做到的,通过正确的工具和一些练习,它将成为您的第二天性。如果您将性能视为日常开发的一部分,而不是事后诸葛亮(那时解决问题的成本会高得多),那么这将非常有帮助。

让我们快速总结一下为什么您真的应该在前期就关心应用程序的性能

  • 如果您的用户获得糟糕的用户体验,感到沮丧并开始抱怨,那么您将不得不花费数天时间进行调试,而不是编写新代码。
  • 成本!通常有两种方法可以使应用程序更快:花费更多钱购买硬件(这在云 Web 世界中尤其如此,糟糕的 SQL 查询可能会导致您不必要地购买整个新的数据库服务器),或者修复您的代码。
  • 一旦性能成为生产环境中的问题,可能需要进行架构重构来修复,这不仅痛苦,还会分散您进行新工作的精力。
  • 能够首次就做得很好,并知道您的用户喜欢使用您的应用程序,这是令人满意的。

我们假设,如果您仍在阅读,您可能已经认识到优化 ASP.NET 应用程序以提高性能的好处。广义地说,有三个方面会影响您应用程序的性能

用户感知

让我们面对现实吧;绝大多数应用程序不需要以极高的速度来构建,它们只需要足够快。尽管如此,我还没遇到过用户不抱怨速度的,无论应用程序有多快。人类是好奇的物种。

然而,需要记住的关键点是,在追踪性能问题时,最重要的视角是用户的视角——始终确保您将精力集中在会影响用户的问题上,而不是您认为可能运行缓慢的代码段。

编译与配置

为了最大限度地发挥应用程序的优势,请正确编译代码并禁用调试功能来配置系统。以非调试模式编译代码并启用最高级别的优化将确保编译器生成更有效的代码(这还有其他影响,但那是另一天的故事)。在像 .NET 这样的字节码系统上,调试功能会阻止 JIT 编译器生成最有效的代码,因为生成的代码将包含额外的调试信息,并且不会完全优化。

同时,请确保您的应用程序和目标环境系统已正确配置以最大化性能。例如,您应该考虑的一些事项包括:将日志级别保持在适合生产系统的适当级别(以免产生不必要的开销),启用压缩(即使是动态内容),并为静态内容制定合理的缓存策略。

代码

最快的代码是根本不运行的代码。然而,由于未执行的代码没有多大用处,因此在编写代码时,应该考虑性能,所以让我们看看如何最大限度地利用我们编写的代码。

工具和技术

我坚信“先让它工作,再让它正确,最后让它快速”(按此顺序)的格言,但即使在开始处理“快速”时,也很容易在错误的地方寻找问题。当然,一个好的起点是通过最佳实践来尝试完全避免最糟糕的问题。

通用最佳实践

与大多数事情一样,实现高性能的关键在于教育。开发人员应该知道并使用良好的编码和架构实践,虽然其中一些指南对你们中的许多人来说是显而易见的,但总是值得回顾一下。当然,遵循这些指南仍然不能保证代码能很好地运行,但它肯定会增加成功的几率!

  • 首先,您应该了解分布式计算的谬误,以便能够避免它们可能给您带来的陷阱。
  • 接下来,您应该了解您的数据结构,何时适合使用它们,以及使用它们在内存和访问时间方面的成本是多少(是常数、线性还是指数……)。
  • 您应该理解访问磁盘的成本与从内存读取数据的成本与从网络读取数据的成本之间的差异,因为速度差异可能相差几个数量级。
  • 但最重要的是,不断学习并遵守普遍认同的开发指南,例如在数据库端进行分页和排序,而不是在代码中进行;适当地使用缓存;确保数据库已正确索引;压缩和捆绑 CSS 和 JavaScript 文件成单个文件以减少网络连接等。为了让您开始,有诸如 Stack Overflow 和其他开发人员经过验证的性能建议汇编之类的资源。Red Gate 发布了一个名为《50 种避免、查找和修复 ASP.NET 性能问题的方法》的免费电子书。

当然,即使遵循了这些指南并使用了最佳实践,错误也难免会发生,您越早发现它们,它们的影响就越小。更重要的是,它们就越容易修复。

在代码开发过程中,我倾向于使用两个工具来捕获大多数问题。首先,许多性能问题源于数据库层面,所以让我们先处理那个层面

SQL Profiler

我在开发过程中使用的一种技术是,当应用程序运行时,让SQL Profiler 始终保持打开状态(为了有效利用它,建议使用多个显示器,以便于同时监控应用程序和数据库),这样我就可以持续跟踪正在执行的查询。通过快速查看 SQL 活动,我可以毫不费力地发现一些问题

  • 调用的次数——如果在单个请求/操作中看到很多查询,那么可能有一些适合缓存的候选,或者可能有一些延迟加载正在起作用,而我应该改为使用即时加载或预取,以减少数据库调用次数。ORM 在开发过程中非常有帮助,但很容易(在不知情的情况下)滥用数据库而不自知。激活一个分析器是保持事物正常运行的好方法。
  • 慢查询——如果我们看到查询花费了太多时间,那么可能缺少索引、查询未优化,或者 JOIN 条件不佳。
  • 数据过载——如果返回了太多的数据,可能是分页缺失或处理不当。
  • 过多的物理读取——如果我看到查询导致大量物理读取,则缺少索引的可能性很高。

从中可以得出的一点是,在开发过程中拥有实际的数据集进行测试非常重要,否则与数据库相关的任何问题(例如数据结构、配置、优化)都将很难发现。

当然,如果您的团队在开发中使用共享数据库(这是您绝对应该考虑的),您应该小心过滤数据,以便只监控您机器/数据库用户发出的调用。

现在您已经掌握了在开发过程中监控应用程序数据存储的方法,您需要一种方法来监控应用程序代码本身,所以让我们继续。

Glimpse

我在开发过程中经常使用的另一个工具是Glimpse。它是一个开源的 ASP.NET 模块(是的,它是免费的),可以让您轻松地看到关于您的应用程序代码和服务器的大量信息。例如,Glimpse 可以让您即时了解请求数据(请求参数、配置的路由、服务器变量、会话)、服务器的配置和环境、调用的管道和方法(包括它们的耗时),以及 AJAX 调用——这仅仅是个开始。

Glimpse 以轻量级且不干扰的方式实现这一点;数据可以直接在 Web 浏览器中查看,信息叠加在窗口的右下角(图 1),随时可用。

图 1 - Glimpse 最小化数据显示在屏幕左下角

在图 2 中,您可以更详细地看到数据摘要;它包含了有关 HTTP 请求以及服务器执行的代码以返回此特定页面的信息。

图 2 - Glimpse 摘要窗口

此摘要始终可用,您可以展开数据以获取更多详细信息,如图 3 所示。从这个示例(即图 3 中呈现页面所需的数据)可以看出,该请求总共花费了 1197 毫秒来服务,其中 39 毫秒用于网络传输信息,84 毫秒由服务器处理,1074 毫秒由客户端渲染页面。

我们立刻可以看出,大约 88% 的时间花在了客户端,所以这可能是我们应该首先优化的方面,因为它影响用户对性能的感知,即使服务器正在以令人称赞的速度工作。

这是一个“不要猜测!”原则的完美例子——付出的努力很小,我们就已经对在哪里寻找性能改进有了更好的了解。

图 3 - 展开摘要(浏览器请求)

这仅仅是冰山一角,因为 Glimpse 不仅提供比我们简要介绍的更深入的细节,它还支持插件系统,因此您可以安装扩展程序来提供特定组件/系统的详细信息。例如,我们可以使用Entity Framework 插件来显示应用程序执行的 SQL 命令及其持续时间和参数(图 4),而不是使用 SQL Profiler。尽管这很方便,但请注意,Glimpse 中的数据(目前)不如 SQL Profiler 提供的信息丰富。

图 4 - 详细信息窗口。查看请求中执行的 SQL(点击可扩展视图)

在撰写本文时,快速查看官方的 NuGet 仓库,我们可以找到39 个 Glimpse 包(即使排除核心包,也有很多扩展),所以很有可能,您会找到一些东西来帮助您快速地在自己的应用程序中定位确切的问题。

将 glimpse 添加到您的 ASP.NET 应用程序,就像从官方NuGet 仓库安装相应的包一样简单,默认情况下它只针对本地服务器工作,因此在生产系统上不会启用。

分析

即使代码开发得很认真,几乎可以肯定某些地方会变慢。例如,在您进行负载测试后,或者当您的应用程序需要意外扩展时,您可能会发现代码的性能或扩展性不如预期。这可能发生,尽管您付出了最大的努力,遵循了最佳实践,并严格应用了性能优化。

正如我们在文章开头讨论过的,当那个时候到来时,最好有一些工具可以让您衡量和理解时间花在哪里,这样您就可以将优化工作集中在真正能产生差异的地方。

如果您的应用程序还没有投入生产,那么您能够未雨绸缪地编写最好的代码,这值得称赞!然而,请记住,您只能依靠(有根据的)猜测来了解它将如何被使用以及它的行为。根据您预期用户的典型画像,尝试预测他们将如何使用该应用程序。进行与预期使用模式一样多的分析会话,然后在此基础上进行。

如果应用程序已经在生产环境中,那么我们就有一些真实的数据,说明最常用的功能是什么,所以我们可以先关注它们(优化一个每月只使用一两次的应用程序的慢部分是前面提到的陷阱之一——浪费资源)。您可以通过分析应用程序日志来找到这些常用功能,或者,如果您的应用程序正在使用遥测应用程序,则可以通过您已有的数据来使用。

一旦您利用数据确定了从哪里开始查找,就首先分析并优化最慢的高使用率函数(以获得最大的收益),然后再次分析,并解决剩余的罪魁祸首,直到优化不再具有成本效益为止。是的,这听起来有点辛苦,但我保证它比猜测和使用计时语句要快得多!

注意:我在这里描述的这些操作可以使用市场上大多数分析器(包括 Visual Studio 内置的功能)来完成;然而,我将使用Red Gate ANTS Performance Profiler 来说明其中的一些观点,因为它是唯一一个允许您将数据访问活动直接关联到触发它的 .NET 代码的工具,这意味着我可以更快地缩小问题的根源,并且麻烦最少。我还发现它在易用性和广泛的功能之间取得了很好的平衡。

与手动计时语句或猜测相比,使用 ANTS Performance Profiler 的好处是,每一次通过分析器的运行都会评估整个应用程序并缩小潜在问题列表。因此,不再需要猜测,也不再需要猜测——它们根本不再需要了。

关于代码分析的一些说明

值得花点时间详细看看分析,因为它可以说是您拥有的最强大的工具。应用程序的分析通常沿着两个不同的方向进行

  • 分析内存使用情况,以查找可能严重影响应用程序性能的内存泄漏。或者,另外,优化内存使用(如果您使用过多内存,应用程序可能会受到物理或虚拟内存的限制)。通过使用更少的内存,您可以减少内存分页,提高进程缓存的使用率,更重要的是,减少对垃圾回收器的压力
  • 性能分析,以查找性能瓶颈和代码热点(花费时间比其他代码路径多的代码路径 - 图 5)。一个好的分析器应该不仅能够理解代码本身的时间消耗,还能够理解何时调用了外部系统(例如通过 HTTP 调用另一个服务器)、关系数据库或排队系统。有了这些信息,如果有一些调用很慢,您至少可以确定问题所在并尝试解决它。

至于对数据库系统的调用,您选择的分析工具应该能够让您检查所做的查询(并希望返回的数据量)。理想情况下,它应该能够理解 SQL(图 6)并能够将相同的查询(带有不同的参数)分组,以便您可以集中精力优化最关键的查询(即调用最多且影响最大的查询)。

图 5 - 查看调用树并查看热点

图 6 - 查看应用程序执行的 SQL 查询

需要明确的是,即使是像ANTS Performance Profiler 这样强大的工具,也可以与前面提到的 SQL Profiler 监控相辅相成。SQL Profiler 是一个专注于单一特定目的的工具,非常适合连续监控。另一方面,ANTS Performance Profiler 旨在让您全面了解您的应用程序,并能够深入到您的 .NET 代码或数据访问层。

结论

让我们简要回顾一下我们讨论过的要点

  1. 您应该在开始编写代码时就开始考虑性能。
  2. 性能是用户感知、应用程序编译/配置以及代码本身(与数据库和外部服务相结合)的结果。
  3. 教育是优化代码的关键——确保您了解并理解常见的最佳实践。
  4. 始终关注您的代码与数据库和其他外部服务的交互方式。您应该在开发过程中这样做,并在进行故障排除和专门优化时牢记这一点。
  5. 使用 Glimpse 和 SQL Profiler 等工具,让您能够持续了解应用程序的性能,并提供数据,以便在发现问题时立即深入分析。
  6. 检查和分析您的应用程序——不要使用猜测、手动计时语句或猜测来找出性能问题所在。使用您可用的工具来全面了解您的整个应用程序,从而为您节省大量时间和痛苦。
© . All rights reserved.