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

良好的性能: 三种阻止它的开发人员行为!

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (19投票s)

2016年5月2日

CPOL

15分钟阅读

viewsIcon

17013

在本文中,我将讨论应用程序的良好性能以及阻碍其发挥的三种开发者行为。

Intel® Developer Zone 提供跨平台应用程序开发、平台和技术信息、代码示例以及同行专业知识的工具和指南,以帮助开发人员进行创新并取得成功。加入我们的 Android物联网 (Internet of Things)Intel® RealSense™ 技术Windows 社区,下载工具、获取开发套件、与志同道合的开发人员分享想法,并参与黑客马拉松、竞赛、路演和本地活动。

引言

性能被认为是应用程序最有价值的非功能性需求之一。如果您正在阅读本文,您可能正在使用浏览器或文档阅读器等应用程序,并理解性能的重要性。在本文中,我将讨论应用程序的良好性能以及阻碍其发挥的三种开发者行为。

行为 #1:对开发技术缺乏理解

无论您是刚毕业的学生还是有多年经验的开发者;在您需要开发某项内容时,您很可能会寻找已有的解决方案。希望是以相同的编程语言编写的。

这并非坏事。事实上,它通常可以加快开发速度。但另一方面,它也可能阻碍您学习新知识。因为通过这种方式,很少会花时间检查代码并不仅理解算法,还理解每一行代码的内部工作原理。

这就是我们开发者陷入第一种行为的一个例子。但还有其他方式。例如,在我年轻、刚开始软件开发之旅时,当时的我的老板是我的榜样,他所做的一切都是最好的。每当我需要做某件事时,我都会看看他是怎么做的,并尽可能地模仿。很多时候,我并不理解他的方法为什么有效,但那又有什么关系呢?它有效就行!

我称一类开发者为“4x4”开发者。他或她会尽最大努力去完成被要求做的任何事情。他们通常会寻找现成的模块或已完成的部分,将它们组合在一起,“ voilà!”事情就完成了!这种开发者很少花时间去理解他们找到的所有部分,也不会考虑或调查可伸缩性、可维护性或性能。

还有一种情况会导致开发者不理解事物的实际工作方式:从不遇到问题!当您第一次使用一项技术时,遇到问题,您就会深入研究该技术的细节,最终您就会理解它是如何工作的。

此时,让我们看一些示例,这些示例将帮助我们理解理解技术与仅仅使用技术之间的区别。由于我主要是一名 .NET* Web 开发人员,我将专注于这方面。

JavaScript* 和文档对象模型 (DOM)

让我们看看下面的代码片段。很简单。代码只是更新 DOM 中某个元素的样式。问题(尽管在现代浏览器中问题不大,但为了说明这一点而包含),是它遍历了 DOM 树三次。如果此代码被重复使用,并且文档很大且复杂,那么应用程序的性能将会受到影响。

修复此类问题很容易。看看下面的代码片段。在操作对象之前,变量myField 中有一个直接的引用。新代码更简洁,阅读和理解起来更快,并且性能更好,因为它只访问一次 DOM 树。

让我们看另一个例子。这个例子摘自:http://code.tutsplus.com/tutorials/10-ways-to-instantly-increase-your-jquery-performance--net-5551

在下图中有两个等效的代码片段。每个代码都创建了千个列表项li 元素。右侧的代码为每个li 元素添加了一个id 属性,而左侧的代码为每个li 元素添加了一个class 属性。

正如您所见,每个代码片段的第二部分只是访问创建的每个千个li 元素。在我对 Internet Explorer* 10 和 Chrome* 48 进行的基准测试中,左侧代码的平均时间为 57 毫秒,右侧代码的平均时间为 9 毫秒——明显更短。在这种情况下,仅仅通过一种方式或另一种方式访问元素,差异是巨大的。

这个例子必须非常谨慎地对待!还有很多其他需要理解的因素可能会使这个例子看起来不正确,例如选择器的评估顺序,是从右到左的。如果您正在使用 jQuery*,请也阅读有关 DOM 上下文的信息。有关 CSS 选择器性能的通用概念,请参阅以下文章:https://smacss.com/book/selectors

让我们提供最后一个 JavaScript 代码示例。这个例子更多地与内存相关,但将帮助您理解事物的实际工作方式。浏览器中高内存消耗也会导致性能问题。

下图显示了两种创建具有两个属性和一个方法的对象的方法。在左侧,类的构造函数将两个属性添加到对象,并通过类的原型添加额外的函数。在右侧,构造函数一次性添加属性和方法。

创建对象后,使用这两种技术创建了千个对象。如果您比较对象使用的内存,您会在 Chrome 中看到两种方法在浅层大小 (Shallow Size) 和保留大小 (Retained Size) 上的内存使用差异。原型方法在浅层大小方面使用的内存少约 20%(20 KB 对 24 KB),在保留内存方面引用少高达 66%(20 KB 对 60 KB)。

为了更好地理解浅层大小和保留大小内存的工作原理,请参阅

https://developers.google.com/web/tools/chrome-devtools/profile/memory-problems/memory-101?hl=en

通过了解技术的工作原理,您可以创建对象。但是,理解技术实际如何工作可以为您提供改进应用程序(如内存管理和性能)的工具。

LINQ

在我准备关于这个主题的会议演示文稿时,我想提供一个服务器端代码的示例。我决定使用 LINQ*,因为 LINQ 已成为 .NET 世界中新开发的首选工具,并且是寻找性能改进的最有希望的领域之一。

考虑一个常见场景。下图中有两个功能上等效的代码段。代码的目的是列出学校中所有部门及其所有课程。在名为 Select N+1 的代码中,我们列出所有部门,并为每个部门列出其课程。这意味着,如果有 100 个部门,我们将向数据库发出 1+100 次调用。

有许多方法可以解决这个问题。一种简单的方法如图像右侧的代码所示。通过使用Include 方法(此处我使用硬编码字符串以便于理解),将只有一次数据库调用,所有部门及其课程将被一次性加载。在这种情况下,当第二个foreach 循环执行时,每个部门的所有 Courses 集合将已在内存中。

通过避免 Select N+1 问题,可以实现性能提高数百倍。

让我们考虑一个不那么明显的例子。

在下图中,两个代码片段之间只有一个区别:第二行的目标列表的数据类型。您可能会问,目标类型有什么区别?当您了解该技术的工作原理时,您会意识到目标数据类型实际上决定了查询何时被执行。这反过来又决定了每个查询的筛选条件何时被应用。

在 Code #1 示例中,其中期望一个IEnumerable,查询将在Take<Employee>(10) 执行之前执行。这意味着,如果 there are 1,000 employees,所有这些员工都将从数据库中检索,然后只取 10 个。

在 Code #2 示例中,查询将一直执行到Take<Employee>(10) 执行。也就是说,数据库只检索 10 条记录。

以下文章详细解释了使用多种集合类型的区别。

https://codeproject.org.cn/Articles/832189/List-vs-IEnumerable-vs-IQueryable-vs-ICollection-v

SQL Server*

在 SQL 中,有很多概念需要理解才能最大限度地提高数据库的性能。SQL Server 很复杂,因为它需要理解数据的使用方式,哪些表被查询得最多,以及通过哪些字段。

尽管如此,您仍然可以应用一些通用概念来提高性能,例如

  • 聚集索引与非聚集索引
  • 正确排序的 JOIN
  • 了解何时使用#temp 表和变量表
  • 视图与索引视图的使用
  • 预编译语句的使用

为简洁起见,我将不提供具体用例,但这些是您可以使用的、理解的并最大化利用的概念。

心态转变

那么,为了避免行为 #1,我们作为开发者必须进行哪些心态上的转变?

  • 停止认为“我是一名前端或后端开发人员!”您可能是一名工程师,并且可能成为某个领域的专家,但不要以此为借口来逃避学习其他领域的知识。
  • 停止认为“让专家来做吧,因为他更快!”在敏捷无处不在的当今世界,我们必须成为可替代的资源,并且必须学习我们薄弱的领域。
  • 停止对自说“我不明白!”当然!如果这很容易,那么我们所有人都会是专家!花时间阅读、提问和理解。这并不容易,但会带来回报。
  • 停止说“我没时间!”好的,我明白这一点。这种情况确实会发生。但一位 Intel 的同事曾经告诉我:“当你对某件事充满热情时,你的带宽是无限的。”而我,现在正在周六凌晨 12 点写这篇文章!

行为 #2:对特定技术存在偏见

我从 1.0 版本开始就在 .NET 中开发。我了解 Web Forms 的每一个细微之处,以及许多 .NET 客户端库(我自定义了一些)。当我看到 Model View Controller (MVC) 出现时,我犹豫是否使用它,因为“我们不需要它”。

我不会继续列出我一开始不喜欢但现在却广泛使用的东西。但这印证了人们对使用特定技术存在偏见,从而阻碍自己获得更好性能的观点。

我经常听到的一种讨论是关于 Entity Framework 中的 LINQ-to-Entities,或者关于查询数据时的 SQL 存储过程。人们已经习惯了其中一种或另一种,并试图继续使用它们。

使人们对某种特定技术产生偏见的另一个方面是他们是开源的爱好者还是仇恨者。这使得人们不考虑什么最适合他们当前的情况,而是什么最符合他们的哲学。

有时外部因素(例如截止日期)会促使我们做出决定。为了选择最适合我们应用程序的技术,我们需要时间来阅读、试用、比较和得出结论。当我们开始开发新产品或现有产品的版本时,我们已经迟到的情况并不少见。解决这种情况有两种方法:站出来要求时间,或者加班学习。

心态转变

那么,为了避免行为 #2,我们作为开发者必须进行哪些心态上的转变?

  • 停止说“这总是有用的”,“我们一直都用这个”,等等。我们需要识别并使用其他选项,特别是当有数据支持这些选项时。
  • 停止“修复解决方案”!有时人们想使用一种不能提供预期结果的特定技术。然后他们花费数小时试图调整该技术。在这种情况下,他们正在“修复解决方案”,而不是专注于问题,并且可能在其他地方找到一个更快、更优雅的解决方案。
  • “我没时间!”当然,我们没有时间学习或尝试新东西。同样,我明白这一点。

行为 #3: 未理解应用程序的基础设施

在我们付出巨大努力创建最佳应用程序后,该部署了!我们已经测试了所有内容。一切都在我们的机器上完美运行。所有 10 位测试人员都对它及其性能非常满意。那么,在这一切之后,有什么可能出错呢?

好吧,一切都可能出错!

您是否问过自己以下任何问题?

  • 应用程序是否需要在负载均衡的环境中工作?
  • 应用程序是否将托管在云端,并且有许多实例?
  • 我的目标生产机器上还有多少其他应用程序在运行?
  • 那台服务器上还有什么在运行?SQL Server?报告服务?某些 SharePoint* 扩展?
  • 我的最终用户在哪里?他们遍布世界各地吗?
  • 未来五年我的应用程序将有多少最终用户?

我明白并非所有这些问题都与基础设施有关,但请忍耐。最终,我们的应用程序将运行的最终条件与我们的暂存服务器并不相同。

让我们选择一些可能影响我们应用程序性能的潜在情况。我们将从遍布全球的用户开始。也许我们的应用程序非常快,并且我们没有收到美国客户的投诉,但我们在马来西亚的客户没有相同的快速体验。

有很多方法可以解决这种情况。例如,我们可以使用内容分发网络 (CDN),我们可以在其中放置静态文件,然后从不同地点加载页面会更快。下图显示了我所说的内容。

选择另一个潜在情况,让我们考虑在同时运行 SQL Server 和 Web 服务器的服务器上运行的应用程序。在这种情况下,我们在同一台机器上有两个 CPU 密集型服务器。那么,我们如何解决这个问题?仍然假设您在 Internet Information Services (IIS) 服务器中运行 .NET 应用程序,我们可以利用 CPU 亲和性。CPU 亲和性将一个进程绑定到一个或多个特定核心。

例如,假设我们在一台有四个 CPU 的机器上运行 SQL Server 和 Web 服务器 (IIS)。

如果我们让操作系统决定 IIS 或 SQL Server 使用哪个 CPU,可能会有各种设置。我们可以为每个服务器分配两个 CPU。

或者我们可以将所有处理器分配给一个服务器!

在这种情况下,我们可能会陷入死锁,因为 IIS 可能正在处理过多的请求,占用了所有四个处理器,并且其中一些请求可能需要访问 SQL,而这当然不会发生。尽管这是一种不太可能的情况,但它说明了我的观点。

还有一个额外的情况:一个进程不会一直运行在相同的 CPU 上。会有过多的上下文切换。这种上下文切换会导致服务器性能下降,然后影响在该服务器上运行的应用程序。

减少这种情况的一种方法是使用 IIS 和 SQL 的处理器亲和性。这样,我们可以确定 SQL Server 和 IIS 各需要多少处理器。这可以通过更改 IIS 的 CPU类别中的处理器亲和性设置以及 SQL Server 数据库中的“亲和性掩码”来完成。这两种情况都显示在下面的图像中。


我还可以继续讨论基础设施层面的其他提高应用程序性能的选项,例如使用 Web Gardens 和 Web Farms。

心态转变

为了避免行为 #3,我们作为开发者必须进行哪些心态上的转变?

  • 停止认为“这不是我的工作!”我们作为工程师,必须尽可能拓宽我们的知识面,以便为客户提供最佳的整体解决方案。
  • “我没时间!”当然,我们总是没有时间。这是心态转变中的共同点。抽出时间来做事情,这决定了一个人是成功、超越还是出类拔萃!

不要感到内疚!

但是,不要感到内疚!这不全是你的错!真的,我们没有时间!我们有家庭,有爱好,我们还需要休息!

这里重要的是要认识到,有时性能不仅仅是编写好的代码。我们都会在一生中表现出其中一些或全部行为。

让我给你一些避免这些行为的建议。

  1. 安排时间。当被要求估算项目时,确保您为研究、测试、得出结论和做出决策预留了时间。
  2. 尝试创建一个个人测试应用程序。这个应用程序将避免您在正在开发的应用程序中尝试新东西。这是我们所有人都会犯的一个错误。
  3. 寻找那些已经知道的人,进行结对编程。在您的基础设施人员部署应用程序时与他们一起工作。这是值得花费的时间。
  4. Stack Overflow 是邪恶的!!!实际上,我确实在那里提供帮助,并且我的大部分问题都已在那里得到解决。但是,如果您使用它来“复制粘贴”答案,您最终将得到不完整的解决方案。
  5. 停止做前端人员。也停止做后端人员。如果您愿意,成为一名主题专家,但要确保您在讨论您不熟悉的领域时能够进行明智的讨论。
  6. 乐于助人!这可能是最好的学习方式。当您花时间帮助人们解决问题时,您最终会通过避免遇到相同或类似的情况来节省自己的时间。

另请参阅

Scott Hanselman 关于成为一名谷歌员工或一名开发者的有趣且引人入胜的博客。

http://www.hanselman.com/blog/AmIReallyADeveloperOrJustAGoodGoogler.aspx

更多关于对象和原型的信息

http://thecodeship.com/web-development/methods-within-constructor-vs-prototype-in-javascript/

关于作者

Alexander García 是来自 Intel 哥斯达黎加的计算机科学工程师。Alex 在软件工程方面拥有超过 14 年的专业经验。他的专业兴趣从软件工程实践、软件安全和性能到数据挖掘及相关领域。Alex 目前正在攻读计算机科学硕士学位。

© . All rights reserved.