在回答这个问题时,我开始怀疑为什么这么多的开发人员认为好的设计不应该考虑性能,因为这样做会影响可读性和/或可维护性。
我相信,一个好的设计在编写时也要考虑性能,并且一个好的设计的优秀开发人员可以编写一个高效的程序,而不会对可读性或可维护性产生不利影响。
尽管我承认存在极端情况,但为什么许多开发人员坚持认为高效的程序/设计将导致较差的可读性和/或较差的可维护性,因此,性能不应作为设计考虑因素?
在回答这个问题时,我开始怀疑为什么这么多的开发人员认为好的设计不应该考虑性能,因为这样做会影响可读性和/或可维护性。
我相信,一个好的设计在编写时也要考虑性能,并且一个好的设计的优秀开发人员可以编写一个高效的程序,而不会对可读性或可维护性产生不利影响。
尽管我承认存在极端情况,但为什么许多开发人员坚持认为高效的程序/设计将导致较差的可读性和/或较差的可维护性,因此,性能不应作为设计考虑因素?
Answers:
我认为这样的观点通常是对过早(微)优化尝试的反应,这种尝试仍然很普遍,而且弊大于利。当一个人试图反驳这种观点时,很容易陷入另一种极端,或者至少看起来像是另一种极端。
但是,随着近几十年来硬件资源的巨大发展,对于当今编写的大多数程序而言,性能不再是主要的限制因素,这是事实。当然,在设计阶段应该考虑预期和可实现的性能,以便确定性能可能成为(主要)问题的情况。然后从一开始就设计性能确实很重要。但是,整体的简单性,可读性和可维护性仍然更为重要。正如其他人指出的那样,与最简单的解决方案相比,性能优化的代码更复杂,更难以阅读和维护,并且更容易出现错误。因此,必须证明花在优化上的任何努力-不仅仅是相信-带来真正的好处,同时尽可能降低程序的长期可维护性。因此,良好的设计可以将复杂的,性能密集的部分与其余的代码隔离开,这些部分应保持尽可能简单和整洁。
来自从事高性能代码开发工作的开发人员方面的问题,设计中需要考虑几件事。
正确处理,美观处理,快速获取。以该顺序。
contains
它,请使用HashSet
而不是ArrayList
。性能可能无关紧要,但是没有理由不这样做。利用好的设计和性能之间的一致性-如果处理某些集合,则尝试一次完成所有操作,这可能会更易读,而且速度更快(可能)。
如果我可以假定“借用” @greengit的漂亮图表,并做一些小的补充:
|
P
E
R
F
O * X <- a program as first written
R *
M *
A *
N *
C * * * * *
E
|
O -- R E A D A B I L I T Y --
我们都被“教导”了权衡曲线。此外,我们都认为我们是这样的最佳的程序员,任何给定的程序,我们写是那么紧它是曲线上。如果计划在进行中,那么一个方面的任何改进必然会在另一个方面产生成本。
以我的经验,程序只能通过调整,调整,锤打,打蜡并变成“代码高尔夫球”来接近任何曲线。大多数程序在各个方面都有很大的改进空间。 这就是我的意思。
正是因为高性能软件组件通常比其他软件组件复杂几个数量级(所有其他条件都相同)。
即使这样也不是很明确,如果性能指标是至关重要的要求,则必须使设计具有适应这些要求的复杂性。危险在于,开发人员浪费时间在相对简单的功能上,试图从其组件中挤出几毫秒的时间。
无论如何,设计的复杂性与开发人员快速学习并熟悉这种设计的能力直接相关,并且对复杂组件中功能的进一步修改可能会导致错误可能无法被单元测试捕获。复杂的设计具有更多的方面和可能的测试用例,可以考虑使100%单元测试覆盖率的目标成为梦a以求的目标。
话虽这么说,但应该注意的是,一个性能不佳的软件组件可能会由于原始编写者的愚昧而愚蠢地编写并且不必要地变得复杂而导致性能不佳((如果只需要一个,那么就进行8个数据库调用来构建单个实体) ,完全不必要的代码,无论如何都将导致单个代码路径等。)这些情况更多的是提高代码质量,并且由于重构而导致性能提高,而不是预期的结果。
但是,假设组件设计良好,它将始终比针对性能进行了优化(其他所有条件都相同)的类似设计良好的组件复杂。
在我看来,性能是实际问题(或要求)时,应该考虑的因素。不这样做往往会导致微优化,这可能会导致代码更加混乱,只是在这里和那里节省了几微秒的时间,从而导致代码的可维护性和可读性降低。相反,如果需要的话,应该专注于系统的真正瓶颈,并着重那里的性能。
重点不是可读性应始终胜过效率。如果从一开始就知道算法需要高效,那么这将是您开发算法的因素之一。
问题是大多数情况下不需要盲目的快速代码。在许多情况下,IO或用户交互导致的延迟要比算法执行引起的延迟多得多。关键是,如果您不知道这是瓶颈,就不要全力以赴,使某些事情变得更有效率。
优化代码的性能通常会使其变得更加复杂,因为它通常涉及以一种巧妙的方式来做事,而不是最直观的方式。更复杂的代码更难维护,其他开发人员也难以采用(这都是必须考虑的成本)。同时,编译器非常擅长优化常见情况。您尝试改善一种常见情况可能意味着编译器不再识别该模式,因此无法帮助您快速编写代码。应该注意的是,这并不意味着编写任何内容而无需担心性能。您不应该做任何明显无效的事情。
重点是不要担心可能会使事情变得更好的小事情。使用事件探查器,可以看到1)您现在拥有的是一个问题,以及2)您将其更改为什么是一项改进。
因为全球变暖的成本(来自由数亿台PC加上大量数据中心设施扩展的额外CPU周期)和(在用户的移动设备上)运行不佳的优化代码所需的平均电池寿命(在大多数情况下很少出现)程序员的表现或同行评价。
这是经济上的负面外部效应,类似于一种被忽略的污染形式。因此,从根本上偏离思考性能的成本/收益比。
硬件设计人员一直在努力为最新的CPU添加省电和时钟缩放功能。程序员应通过不浪费每个可用的CPU时钟周期,让硬件更频繁地利用这些功能。
添加:在远古时代,一台计算机的成本为数百万美元,因此优化CPU时间非常重要。然后,开发和维护代码的成本变得大于计算机的成本,因此与程序员的工作效率相比,优化已不受欢迎。但是,现在,另一成本正变得比计算机成本高,给所有这些数据中心供电和冷却的成本现在正变得高于内部所有处理器的成本。
我认为很难实现这三个目标。我认为两个可行。例如,我认为在某些情况下可以实现效率和可读性,但是对于微调的代码而言,可维护性可能很难。地球上最高效的代码通常将缺乏可维护性和可读性,这对大多数人来说可能是显而易见的,除非您是那种能够理解英特尔使用内联汇编编写的手工SoA矢量化,多线程SIMD代码的人,行业中使用的最先进的算法,仅在2个月前发表了40页的数学论文,以及12种有价值的代码库,用于一种极其复杂的数据结构。
微观效率
我建议可能与流行观点相反的一件事是,最智能的算法代码通常比最微调的直接算法更难维护。我要挑战的想法是,可伸缩性改进带来了微调代码(例如:缓存友好的访问模式,多线程,SIMD等)带来的巨大收益,至少在一个极其复杂的行业工作过数据结构和算法(可视化FX行业),尤其是在网格处理等领域,因为爆炸的影响可能很大,但引入新的算法和数据结构带来的损失却是巨大的,因为它们是品牌之前从未听说过新。而且,我
因此,我认为算法优化始终胜过与内存访问模式相关的优化这一想法一直是我不太同意的事情。当然,如果您使用的是气泡排序,那么任何微优化都无法为您提供帮助...但是在一定程度上,我认为这并非总是那么明确。而且可以说算法优化比微优化更难维护。我会发现,与Dreamwork的OpenVDB代码(用于算法加速流体模拟的最先进方法)相比,英特尔的Embree采用经典而直接的BVH算法并对其进行微调,维护起来要容易得多。因此,至少在我的行业中,我希望看到更多熟悉计算机体系结构的人更多地进行微优化,就像英特尔进军现场那样,而不是提出成千上万的新算法和数据结构。通过有效的微优化,人们可能会发现越来越少的发明新算法的理由。
我在旧版代码库中工作,在此之前,几乎每个单个用户操作背后都有自己独特的数据结构和算法(总共有数百个奇异的数据结构)。而且它们大多数具有非常偏斜的性能特征,适用范围非常狭窄。如果该系统能够围绕数十个更广泛应用的数据结构旋转,那将容易得多,而且我认为,如果对它们进行更好的微优化,情况可能会如此。我之所以提到这种情况,是因为微优化可以在这种情况下极大地改善可维护性,如果这意味着数百种微悲观的数据结构之间的差异,这些数据结构甚至不能安全地用于严格的只读目的,这涉及到遗漏的缓存和对与
功能语言
同时,我遇到过的一些最可维护的代码相当有效,但由于用功能语言编写,因此极难阅读。在我看来,一般而言,可读性和超级可维护性是相互矛盾的想法。
很难一次性使代码具有可读性,可维护性和高效性。通常,您必须在这三个(如果不是两个)之一中进行一些折衷,例如,为了提高可维护性而降低可读性,或者为了提高效率而降低可维护性。当您寻求其他两个方面时,通常会损害可维护性。
可读性与可维护性
现在,正如我所说,我认为可读性和可维护性不是和谐的概念。毕竟,对于我们大多数人来说,最易读的代码非常直观地映射到人类的思维模式,而人类的思维模式天生就容易出错:“ 如果发生,请执行。如果发生,请执行。否则,请这样做。 ,我忘记了一些事情!如果这些系统相互交互,则应该发生这种情况,以便该系统可以执行此操作...等等,触发该事件时该系统如何处理?“我忘了确切的报价,但有人曾经说过,如果罗马像软件那样建造,那么只需将鸟降落在墙上才能将其推倒。大多数软件就是这种情况。它比我们经常关心的要脆弱得多。想想看,这里和那里的几行看似无害的代码可能会使它停顿下来,以至于让我们重新考虑整个设计,而旨在尽可能可读的高级语言也不是这种人为设计错误的例外。 。
纯粹的功能语言几乎可以做到这一点(几乎无法克服,但是比大多数语言更接近)。这部分是因为它们没有直观地映射到人类思想。它们不可读。它们迫使我们产生思维模式,这使我们不得不使用尽可能少的知识并在不引起任何副作用的情况下,以尽可能少的特殊情况解决问题。它们具有极强的正交性,它们使代码可以经常更改和更改而不会出现突如其来的史诗般的变化,以至于我们不得不在绘图板上重新考虑设计,甚至到改变我们对整体设计的看法,而不必重写所有内容。似乎没有比这更容易维护了……但是代码仍然很难阅读,
还有一些著名的高度优化的代码片段,将使大多数人不知所措,这支持了高度优化的代码难以阅读和理解的情况。
这是我认为最著名的。取自Quake III Arena,并归因于John Carmak,尽管我认为此功能已经进行了多次迭代,但它并不是他最初创建的(Wikipedia很棒吗?)。
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}