我是一名初级软件开发人员,我想知道什么时候是优化软件以提高性能(速度)的最佳时机。
假设该软件不是非常庞大且难以管理,是花更多的时间在开始对其进行优化上还是我应该只是开发能够正确执行所有功能的软件,然后继续对其进行优化以获得更好的性能?
我是一名初级软件开发人员,我想知道什么时候是优化软件以提高性能(速度)的最佳时机。
假设该软件不是非常庞大且难以管理,是花更多的时间在开始对其进行优化上还是我应该只是开发能够正确执行所有功能的软件,然后继续对其进行优化以获得更好的性能?
Answers:
第一件事应该永远是可读性。如果速度慢但可读,我可以修复它。如果它损坏但可读,我可以修复它。如果难以理解,我必须问别人这应该怎么做。
当您仅专注于可读性时,代码的性能就非常出色。如此之多,所以我通常会忽略性能,直到给出需要注意的理由为止。那不应该意味着我不在乎速度。我做。我刚刚发现,很少有问题,在难以阅读时其解决方案实际上更快。
只有两件事使我退出此模式:
无论如何,都要让自己认为您不应该尝试解决方案,因为它可能不是最快的,从而避免了分析瘫痪。如果您尝试多种解决方案,您的代码实际上将受益,因为进行更改将迫使您使用易于更改的设计。可以在以后实际需要的时候使灵活的代码库更快。选择灵活的速度,您可以选择所需的速度。
如果需要一定水平的性能(非功能性要求),那么从一开始就应该是设计目标。例如,这可能会影响哪些技术可能适用,或影响程序中数据流的结构。
但是通常,在编写代码之前不可能进行优化:首先使其工作,然后使其正确,最后使其快速。
在实现大多数功能之前进行优化的一个大问题是,您已将自己锁定在错误的地方进行次优的设计决策。在可维护性和性能之间经常(但不一定)要进行权衡。程序的大多数部分与性能完全无关!典型程序只有几个真正值得优化的热点。因此,在所有不需要性能的地方牺牲性能的可维护性是非常糟糕的选择。
优化可维护性是更好的方法。如果你花你的聪明的可维护性和明确的设计,你会发现,从长远来看,以确定关键的部分更容易,安全地优化它们不影响整体设计。
什么时候是优化软件以获得更好性能(速度)的最佳时间。
首先从您的思想中删除性能就是速度的概念。 性能是用户认为的性能。
如果使应用程序对鼠标单击的响应速度快两倍,并且从十微秒变为五微秒,那么用户将不在乎。如果使应用程序对鼠标单击的响应速度快两倍,并且您的响应时间从4000年延长到2000年,那么用户将不在乎。
如果您使应用程序的运行速度快两倍,并且用完了计算机上的所有内存而崩溃,那么用户将不在乎它现在的运行速度快两倍。
性能是对资源消耗进行有效权衡以实现特定用户体验的科学。用户的时间是重要的资源,但它绝不仅仅是“更快”。实现性能目标几乎总是需要权衡的,它们通常是在时间上换取空间,反之亦然。
假设软件不是非常庞大且管理复杂
那是一个可怕的假设。
如果软件不是很大且管理起来很复杂,那么它可能无法解决用户关心的有趣问题,并且可能超级容易优化。
将更多的时间花在开始优化它上还是更好?还是我应该开发能够正确执行所有功能的软件,然后继续对其进行优化以获得更好的性能?
您坐在那里空白页,然后写void main() {}
:您开始优化吗?没有什么可以优化的!正确的顺序是:
如果您尝试以任何其他顺序执行操作,最终将得到一团糟的错误代码,现在您有了一个程序,该程序可以非常迅速地产生错误的答案并抵抗更改。
但是那里缺少一个步骤。在真正正确的顺序是:
通常,最好是稍后对性能进行优化,但是我已经看到许多项目在开发人员意识到他们最终使用的软件会变得很糟糕时,该软件在添加任何重要负载或数据时会变慢。
因此,我认为最好采用中间立场。不要过多地强调它,但不要完全忽略性能。
我将举一个我已经看过很多次的例子。给定一个ORM库,我们有一个User实体,该实体可以具有一个或多个Order。让我们循环一个用户的所有订单,并找出该用户在我们商店中花费了多少-天真的方法:
User user = getUser();
int totalAmount;
for (Order o : user.getOrders()) {
totalAmount += o.getTotalAmount();
}
我见过开发人员在写类似的东西时,没有考虑任何含义。首先,我们获得用户,希望它只是User表上的一个SQL查询(但可能涉及更多),然后遍历订单,其中可能包括获取订单上所有订单行的所有相关数据,产品信息等-所有这些只是为每个订单获取一个整数!
这里的SQL查询数量可能会让您感到惊讶。当然,这取决于实体的结构。
在这里,正确的方法很可能是添加一个单独的函数,以通过使用ORM提供的查询语言编写的单独查询从数据库中获取总和,我主张第一次这样做,而不是推迟这样做为以后; 因为如果这样做,您最终可能会遇到很多其他问题需要解决,并且不确定从哪里开始。
系统整体性能是系统组件整体之间复杂相互作用的产物。这是一个非线性系统。因此,性能将不仅取决于组件的个别性能,还取决于组件之间的瓶颈。
显然,如果尚未构建系统的所有组件,则无法测试瓶颈,因此您无法在早期就进行非常好的测试。另一方面,构建系统后,您可能会发现进行所需的更改以达到所需的性能并不容易。所以这是一个真正的Catch-22。
使事情变得更加困难的是,当您切换到类似生产的环境时,您的性能配置文件可能会发生巨大变化,而这种环境通常在早期就不可用。
所以你会怎么做?好吧,几件事。
务实。尽早,您可以选择使用“最佳实践”性能平台功能;例如,利用连接池,异步事务并避免有状态性,这可能是多线程应用程序的死亡,在该线程中,不同的工作人员争夺共享数据的访问权。通常,您不会测试这些模式的性能,只是从经验中知道哪种方法有效。
反复进行。当系统相对较新时,请采取基准性能衡量标准,并偶尔进行重新测试以确保新引入的代码不会使性能降低太多。
不要过早优化。您永远都不知道什么是重要的以及什么都不重要;例如,如果您的程序一直在等待I / O,则超快速字符串解析算法可能无济于事。
特别是在Web应用程序中,您可以将重点放在性能上而不是可伸缩性上。如果应用程序可以扩展,则性能几乎无关紧要,因为您可以一直将节点添加到服务器场中,直到速度足够快为止。
特别注意数据库。由于事务完整性约束,数据库往往成为控制系统每个部分的瓶颈。如果您需要高性能的系统,请确保您有才华横溢的人在数据库方面进行工作,审阅查询计划并开发表和索引结构,以使常见的操作尽可能高效。
这些活动大多数不是在项目开始或结束时进行的,而必须持续进行。
我是一名初级软件开发人员,我想知道什么时候是优化软件以获得更好性能(速度)的最佳时机。
了解有两个非常不同的极端。
第一个极端是影响设计很大一部分的事情,例如如何将工作拆分为多少个进程和/或线程以及如何进行通信(TCP / IP套接字?直接函数调用?),是否实现高级JIT。或“一次一个操作码”的解释器,或是否计划将数据结构设计为适合SIMD,或...这些事情往往会对实施产生重大影响,并且在改型后变得非常困难/昂贵。
另一个极端是微优化-到处都是微小的细微调整。这些东西几乎对实现几乎没有影响(并且通常最好由编译器来完成),并且只要您愿意就进行这些优化都是微不足道的。
在这些极端之间是一个巨大的灰色区域。
真正归结为经验/受过教育的猜测被用来回答“使收益证明成本合理”的问题。如果您经常猜错,对一个极端/接近一个极端的优化意味着将所有工作都花光,然后从头开始或从项目失败中重新开始(不必要的过于复杂的设计花费了太多时间)。在/接近另一个极端时,将其保留很明智,直到您能够使用度量(例如,分析)证明它很重要。
不幸的是,我们生活在这样一个世界中,太多的人认为优化只包括了“琐碎”的极端情况(大多数是无关紧要的)。
编写既不可靠也不可维护的代码是最容易的。编写porformant代码更加困难。编写可维护的代码更加困难。而编写可维护和高性能的代码是最困难的。
现在,很明显,这取决于您所制造的系统的类型,某些系统对性能至关重要,并且需要从一开始就计划好。对于像埃里克·利珀特(Eric Lippert)这样的超级有才干的人,上述系统可能很常见;但是对于我们大多数人来说,它们只是我们构建的系统中的少数。
但是,考虑到现代硬件的状况,在大多数系统中,不需要从一开始就特别关注优化,而是避免性能破坏通常就足够了。我的意思是,避免做一些愚蠢的事情,例如带回表的所有记录以获取计数,而不仅仅是查询select count(*) from table
。只是要避免犯错误,并努力了解您所使用的工具。
接下来,首先关注使您的代码可维护。我的意思是:
当统计数据表明需要维护时,可维护代码更容易优化。
接下来,请确保您的代码中有很多自动测试,这有很多好处。更少的错误意味着在需要时有更多的时间进行优化。另外,当您进行优化时,由于可以更快地找到实现中的错误,因此可以更快地迭代并找到最佳解决方案。
自动化的部署脚本和脚本化的基础结构对于性能调整也非常有用,因为它们再次使您可以更快地进行迭代。更不用说它的其他好处了。
在诸如图像处理和光线跟踪等对性能至关重要的领域中,我可能会有偏见,但我仍然会说要“尽可能晚地”优化。无论您的要求对性能有多重要,在进行测量后,事后看来,信息和清晰度总是比事前要多得多,这意味着即使最有效的优化通常也要在获得这些知识后再进行。
特殊情况
但是有时在某些特殊情况下, “尽可能晚”仍然很早。例如,如果我们在谈论离线渲染器,则用于实现性能的数据结构和技术实际上会渗入用户端设计中。这听起来可能令人作呕,但该领域非常前沿且对性能至关重要,因此用户接受了特定于适用于特定光线跟踪器的优化技术的用户端控件(例如:辐照度缓存或光子贴图),因为其中一些已使用需要等待数小时才能渲染图像,而其他人则习惯于浪费大量资金来租用或拥有带有渲染专用机器的渲染农场。如果有竞争力的脱机渲染器可以显着减少渲染时间,那么这些用户的时间和金钱将大大减少。在此区域中,时间减少5%实际上可以激发用户的兴趣。
在这种特殊情况下,您不能随便选择一种渲染技术,而是希望稍后对其进行优化,因为整个设计(包括用户端设计)都围绕您使用的数据结构和算法进行。您甚至不一定会选择对其他人有效的方法,因为在这里,您(作为个人)以及您的特殊优势和劣势,在很大程度上构成了提供竞争解决方案的因素。Arnold背后的主要开发人员的思维方式和敏感性与使用非常不同方法的VRay团队不同。他们不一定能交换位置/技术并做到最好(即使他们都是工业领导者)。您必须进行某种实验,原型和基准测试,然后找到所需的 如果您希望提供可以实际销售的具有竞争力的产品,那么鉴于无穷无尽的尖端技术,尤其擅长做这些事情。因此,在这种特殊情况下,性能问题甚至在开始开发之前就成为最重要的问题。
但这并不一定违反“尽可能晚”优化的要求,在这些极端而特殊的情况下,“尽可能晚”才是更早的。找出何时以及什么不需要这些早期性能问题(如果有的话),可能是开发人员面临的主要挑战。不优化的内容可能是在开发人员的职业中学习和不断学习的最有价值的东西之一,因为您会发现不乏想要优化所有内容的天真的开发人员(不幸的是,即使是一些设法以某种方式保持工作的资深人士)尽管它们适得其反)。
越晚越好
也许最困难的部分是试图理解其含义。我仍在学习,并且我已经进行了近三十年的编程。但是尤其是在我的第三个十年中,我开始意识到这并不困难。如果您将重点放在设计而非实现上,那么这不是火箭科学。您的设计越多地留出喘息的空间,以便以后在不更改设计的情况下进行适当的优化,则可以进行优化的越晚。寻求这样的设计给我提供了喘息的空间,并且我获得了越来越多的生产力。
提供呼吸空间以供日后优化的设计
如果我们可以运用一些“常识”,那么在大多数情况下,这类设计实际上并不难实现。作为个人故事,我喜欢视觉艺术(我发现为艺术家自己编写软件来一定程度上了解他们的需求和说他们的语言对编程软件有所帮助),并且在2000年代初期我花了一些时间使用Oekaki小程序在线作为涂鸦和分享我的作品并与其他艺术家联系的一种快捷方式。
特别是我最喜欢的网站和小程序中充斥着性能缺陷(任何不平凡的画笔大小都会使抓取速度变慢),但是社区非常好。要解决性能问题,我使用了很小的1或2像素画笔,只是这样对我的作品进行了涂鸦:
同时,我一直在为软件作者提供改进性能的建议,并且他注意到我的建议在讨论内存优化和算法等方面具有特别的技术性。所以他实际上问我是否是一名程序员,我说是的,他邀请我从事源代码的工作。
因此,我查看了源代码,对其进行了运行,对其进行了概要分析,让我感到震惊的是,他围绕“抽象像素界面”的概念设计了该软件,例如IPixel
,最终导致了所有热点领域背后的根本原因,每个图像的每个像素的分配和分配。然而,没有一种可行的方法可以在不重新考虑整个软件设计的情况下对其进行优化,因为当我们的抽象在单个抽象像素的粒度级别上工作时,该设计将他困在一个角落,除了微不足道的微优化之外,没有太多东西一切都取决于这个抽象像素。
我认为这违反了“常识”,但对于开发人员来说显然不是那么常识。但这就像不要在粒度级别上抽象事物一样,即使最基本的用例也要被数以百万计的实例化,例如巨大的陆军模拟中的像素,粒子或微小单元。赞成IImage
(您可以在较大的聚合级别处理所需的所有图像/像素格式),也IParticleSystem
可以选择IPixel
或IParticle
,然后可以在此类接口和工具后面放置最基本,最快速且易于理解的实现。拥有所有以后需要优化的呼吸空间,而无需重新考虑整个软件的设计。
正如我最近看到的那样,这就是目标。除了上面的脱机渲染器之类的特殊情况外,设计时要有足够的呼吸空间以尽可能晚地进行优化,并提供尽可能多的事后信息(包括测量值),并尽可能晚地应用任何必要的优化。
当然,我并不一定建议在输入上使用二次复杂度算法,这些输入在常见的用户端情况下很容易达到非平凡的大小。到底是谁做的?但我什至认为,如果以后可以轻松实现该实现,那就没什么大不了的。如果您不必重新考虑任何设计,那仍然不是一个严重的错误。
我将建议性能不仅仅是速度。它包括规模(数百到数千个并发用户)。当然,您不希望应用程序在承受生产负荷时立即运行。性能包括应用程序消耗多少资源(例如内存)。
性能也易于使用。有些用户宁愿在10秒内执行一次按键操作,而不是在1秒内进行2次按键操作。对于此类问题,请咨询您的设计负责人。我不喜欢这样的东西提早给用户。在真空中,他们可能会说X,但一旦使用功能性预发布功能,他们可能会说Y。
最佳的个人速度是保存资源(例如数据库连接)。但是对于规模,您应该尽可能晚地获取连接并尽快释放它。一次访问数据库以获取3件东西的速度快于3次单独访问数据库的速度。
您是否正在旅行以获取在会话期间不会更改的信息。如果是这样,则在会话开始时获取并保留它是内存。
选择收集类型时,请考虑功能,速度和大小。
您确定需要将这些物品放在收藏夹中吗?常见的问题是将文件中的所有行读入列表,然后一次处理列表中的一行。一次只读取一行并跳过列表,效率更高。
当您可以循环一次并执行三件事时,您是否循环了三次?
是否有一个地方,您可能需要通过回叫在另一个线程上进行处理。如果这样,则在不干扰当前设计需求的情况下,将代码打包为可能的需求。
很多性能也是干净的代码。
有过早的优化,并且只是在做常识性事情,实际上并不需要花费更多时间。
在数据库中,我看到过早的优化。在出现速度问题之前,将对速度进行非标准化。我得到的论据是,如果我们稍后更改表,则必须更改所有内容。通常,您可以创建一个视图,以这种方式呈现数据,以后可能需要将其交换为非规范化表。