当我编写代码时,我总是试图使我的代码尽可能的干净和可读。
偶尔会有一段时间,您需要越界,从漂亮的干净代码变为稍微难看的代码,以使其更快。
什么时候可以越过那条线?
当我编写代码时,我总是试图使我的代码尽可能的干净和可读。
偶尔会有一段时间,您需要越界,从漂亮的干净代码变为稍微难看的代码,以使其更快。
什么时候可以越过那条线?
Answers:
当你越界
这是一个真实的示例:我正在运行的实验系统生成数据的速度太慢,每次运行耗时超过9个小时,并且仅使用40%的CPU。我没有将代码过多地弄乱,而是将所有临时文件都移到了内存中的文件系统中。添加了8行新的非丑陋代码,现在CPU利用率超过98%。问题解决了; 无需丑陋。
foo
并将其重命名foo_ref
-通常它foo
位于源文件的上方。在我的测试工具我打电话foo
,并foo_ref
进行验证和相关性能测量。
这是错误的二分法。您可以使代码快速且易于维护。
做到这一点的方法是将其编写得干净整洁,尤其是使用尽可能简单的数据结构。
然后,找出时间消耗在哪里(通过运行它,在编写之后,而不是在之前),并逐一修复它们。 (这是一个例子。)
补充:我们总是听到有关权衡的问题,例如时间和内存之间的权衡,还是速度和可维护性之间的权衡?尽管这样的曲线很可能存在,但是不应假定任何给定的程序在曲线上,甚至在它附近的任何地方。
曲线上的任何程序都可以很容易地(通过提供给某种类型的程序员)变得既慢得多,又难以维护,那么它将离曲线很远。这样,这样的程序就有很大的空间可以更快,更易于维护。
以我的经验,这就是许多程序开始的地方。
在OSS的存在中,我做了很多针对性能的库工作,这些工作与调用者的数据结构(即库外部)紧密相关,(根据设计)对传入类型没有任何要求。在这里,使这种性能最佳的最好方法是元编程,因为我在.NET领域(因为我在.NET领域)所以意味着IL发射。那是一些丑陋的代码,但是非常快。
这样,我很高兴地接受到库代码可能比应用程序代码“丑陋” ,只是因为它对输入的控制较少(或没有控制),所以需要通过不同的机制来完成一些任务。或者就像我前几天所说的那样:
“在精神错乱的悬崖上编码,因此您不必 ”
现在,应用程序代码略有不同,因为这是“常规”(健全)开发人员通常将大量的协作/专业时间投入的地方。每个人的目标和期望(IMO)略有不同。
IMO,答案上面表明,它可以快速且易于维护指的是应用程序代码,其中开发人员拥有对数据结构的更多的控制,而不是使用像元编程工具。也就是说,执行元编程的方式不同,疯狂程度不同,开销也不同。即使在那个舞台上,您也需要选择适当的抽象级别。但是,当您积极,积极地,真正地希望它以绝对最快的方式处理意外数据时;它可能会变得丑陋。处理; p
当您分析了代码并验证了它实际上引起了显着的减速。
干净的代码不一定是快速执行代码所独有的。编写通常难以阅读的代码是因为它编写起来更快,而不是因为它执行得更快。
试图编写“肮脏的”代码以使其更快,这无疑是不明智的,因为您不确定您所做的更改实际上可以改善任何事情。Knuth说得最好:
“我们应该忘掉效率低下的情况,大约有97%的时间是这样:过早的优化是万恶之源。但是,我们不应该在那3%的临界水平上放弃自己的机会。优秀的程序员不会因此而感到自满推理时,明智的做法是仔细查看关键代码;但是只有在确定了该代码之后, ”
换句话说,首先编写干净的代码。然后,分析生成的程序,并查看该段实际上是否是性能瓶颈。如果是这样,请根据需要对本节进行优化,并确保包含大量文档注释(可能包括原始代码)以解释优化。然后分析结果以验证您确实进行了改进。
由于问题是“ 难以阅读的快速代码”,因此永远不会提供简单的答案。编写难读的代码从来没有任何借口。为什么?有两个原因。
当它是一次性代码时。我的意思是,从字面上:当你写一个脚本来执行一次性计算或任务,这样的肯定知道你将永远不会再这样做那个动作,你可以“RM源文件”没有犹豫,那么你可以选择丑陋的路线。
否则,这是错误的二分法-如果您认为需要使其变得丑陋以使其更快地执行,则说明您做错了。(或者您需要修改关于什么是好的代码的原则。实际上,当goto是解决问题的适当方法时,使用goto确实很优雅。但是很少这样做。)
每当市场上较低性能的估计成本大于所讨论代码模块的代码维护估计成本时。
人们仍然会扭曲手编码的SSE / NEON /等。尝试在今年流行的CPU芯片上击败竞争对手的软件。
对我来说,这是一定比例的稳定性(例如水泥中的水泥,烤箱中烘烤的粘土,石头中的石头,用永久墨水书写的)。您的代码越不稳定,因为将来需要更改它的可能性就越高,则保持湿润的生产力(如湿粘土)就越需要柔韧性。我还强调柔韧性而不是可读性。对我来说,更改代码比阅读代码更重要。代码很容易阅读,并且需要更改,这是一场噩梦;如果代码需要更改,它们可以读取并轻松理解实现细节有什么用?除非只是学术活动,否则通常容易理解生产代码库中代码的目的是为了能够根据需要更轻松地对其进行更改。如果很难改变 那么可读性的许多好处就荡然无存了。可读性通常仅在柔韧性的情况下有用,而柔韧性仅在不稳定的情况下有用。
自然,即使是最难维护的可想象的代码,无论它有多难读,如果没有理由只更改它,也不会造成问题。而且有可能达到这样的质量,特别是对于性能往往最重要的低级系统代码。我有我经常使用的C代码,自80年代末以来就没有改变过。从那时起,它不需要更改。该代码很笨拙,是在繁琐的日子里写的,我几乎不懂。但是它今天仍然适用,并且我不需要了解它的实现就可以充分利用它。
彻底编写测试是提高稳定性的一种方法。另一个是去耦。如果您的代码不依赖其他任何内容,那么更改它的唯一原因就是它本身是否需要更改。有时,少量的代码重复可以用作一种去耦机制,从而以显着提高稳定性的方式进行折衷,如果作为交换,您获得的代码现在完全独立于其他任何事物,则可以进行折衷。现在,该代码已不受外部环境的影响。同时,依赖于10个不同外部库的代码在将来进行更改的原因是其10倍。
在实践中的另一件有用的事情是将您的库与代码库的不稳定部分分开,甚至可能像单独构建第三方库那样将其分开构建(同样,第三方库也应被使用,不得更改,至少不被您的第三方使用)球队)。只是这种类型的组织可以防止人们对其进行篡改。
另一个是极简主义。您的代码尝试执行的次数越少,就越有可能执行良好的工作。整体设计几乎永久不稳定,因为越来越多的功能被添加到它们中,它们看起来越不完整。
每当您打算编写不可避免地难以更改的代码(例如已微调至死的并行化SIMD代码)时,稳定性就应该是您的主要目标。您可以通过最大化不必更改代码的可能性来抵消维护代码的困难,因此将来不必维护代码。无论代码维护多么困难,这都将维护成本降低到零。