可以用可读代码替换优化代码吗?


78

有时,您会遇到必须扩展/改进某些现有代码的情况。您会看到旧代码非常精简,但是它也很难扩展,而且花时间阅读。

用现代代码代替它是一个好主意吗?

一段时间以前,我喜欢精益方法,但现在看来,最好牺牲很多优化,而采用更高的抽象,更好的接口和更易读,可扩展的代码。

编译器似乎也越来越好,因此诸如之类struct abc = {}的东西被悄无声息地变成了memsets,shared_ptrs产生的代码几乎与原始指针扭曲相同,模板工作得很好,因为它们产生了超精简代码,依此类推。

但是,有时您仍然会看到基于堆栈的数组和带有一些晦涩逻辑的旧C函数,通常它们不在关键路径上。

如果您必须以某种方式触摸其中的一小部分代码,那么更改这种代码是一个好主意吗?


20
可读性和优化在大多数情况下都不反对。
deadalnix 2012年

23
通过一些注释可以提高可读性吗?
YetAnotherUser 2012年

17
它担心OOP-ification被认为是“现代密码”
詹姆斯·

7
就像slackware的哲学一样:如果它没有坏,就不要修复它,至少您有一个非常非常好的理由这样做
osdamv 2012年

5
优化代码是指实际的优化代码还是所谓的优化代码?
dan04 2012年

Answers:


115

哪里?

  • 在Google规模的网站的首页上,这是不可接受的。保持事情尽快进行。

  • 在每年一个人使用一次的应用程序的一部分中,为了获得代码的可读性而牺牲性能是完全可以接受的。

通常,您正在处理的代码部分的非功能性要求是什么?如果某个动作必须在900毫秒内执行。在给定的上下文(机器,负载等)中,有80%的时间,实际上,它的执行时间不到200毫秒。当然,即使可能会稍微影响性能,但也有100%的时间使代码更具可读性。另一方面,如果同一动作在十秒钟之内从未执行过,那么,您应该尝试查看性能(或首先是要求)出了什么问题。

另外,提高可读性将如何降低性能?通常,开发人员正在使行为接近于过早的优化:他们害怕提高可读性,认为这样做会极大地破坏性能,而更具可读性的代码将花费几微秒的时间来执行相同的操作。


47
+1!如果您没有数字,请获取一些数字。如果您没有时间获取数字,则没有时间进行更改。
Tacroy 2012年

49
通常,开发人员常常基于神话和误解来“优化”,例如,假设“ C”比“ C ++”要快,并且避免C ++功能是出于普遍的感觉,即没有数字支持就可以更快。让我想起了我所关注的C开发人员,他认为goto它比for循环要快。具有讽刺意味的是,优化器在for循环方面做得更好,因此他使代码既慢难于阅读。
Steven Burnap 2012年

6
我没有添加其他答案,而是对该答案+1了。如果理解代码片段很重要,请对其进行注释。我在C / C ++ / Assembly环境中工作,使用了已有十年历史的遗留代码,并拥有数十名贡献者。如果该代码有效,则不要理会它,然后重新开始工作。
克里斯K

这就是为什么我倾向于只编写可读代码的原因。可以减少几个热点来达到性能。

36

通常不会

更改代码可能会导致系统中其他地方发生无法预料的连锁问题(如果您没有进行坚固的单元测试和冒烟测试,有时可能在项目的后期才被注意到)。我通常会遵循“如果还没有破裂,就不要修复它”的思路。

该规则的例外情况是,如果您要实现一个涉及此代码的新功能。如果那时候没有意义并且确实需要进行重构,那么只要估计中考虑了重构时间(以及足够的测试和缓冲区来处理连锁问题),就去做。

当然,profile,profile,profile,尤其是在关键路径区域时。


2
是的,但是您认为需要优化。我们不知道是否总是这样,所以我们可能想先确定一下。
haylem 2012年

2
@haylem:不,我认为代码按原样工作。我还假设重构代码将始终会导致系统中其他地方的连锁问题(除非您要处理的代码块没有任何外部依赖性)。
Demian Brecht 2012年

这个答案有一些道理,具有讽刺意味的是,这是因为开发人员很少记录,理解,交流甚至很少注意连锁问题。如果开发人员对过去发生的问题有更深入的了解,他们将知道要衡量的内容,并对更改代码更有信心。
rwong

29

简而言之:取决于

  • 您是否真的需要或使用重构/增强版本?

    • 是否有具体的收益,无论是短期的还是长期的?
    • 这仅仅是为了可维护性,还是真正的体系结构?
  • 真的需要优化吗?

    • 为什么?
    • 您需要追求什么目标收益?

详细介绍

您需要清理干净的东西吗?

这里有些事情要小心,您需要确定真实的,可衡量的收益与您个人的喜好以及不应该接触的潜在不良习惯之间的界限。

更具体地讲,知道这一点:

发生过度工程

这是一种反模式,它带有内置的问题:

  • 可能更具扩展性,但扩展起来并不容易
  • 可能不容易理解
  • 最后但并非最不重要的一点:您可能会降低整个代码的速度。

有人可能还会提到KISS原则作为参考,但是这里有悖常理:优化方式是简单方式还是整洁的架构方式?答案不一定是绝对的,如以下其余部分所述。

你不会需要它

YAGNI原则是不与其他问题完全正交的,但它有助于问自己一个问题:你会需要它?

除了看上去更易于维护之外,更复杂的体系结构真的为您带来好处吗?

如果没有破裂,请不要修复

将其写在一张大海报上,并将其悬挂在屏幕旁边,工作中的厨房区域或开发人员会议室中。当然,还有许多其他的原则值得您重复一遍,但是,当您尝试进行“维护工作”并感到有“改进”的欲望时,这一特殊的要求就很重要。

当我们通读代码以试图理解它时,我们很自然地想要“改进”代码,甚至只是不自觉地触摸它。这是一件好事,因为这意味着我们会固执己见,并试图对内部构造有更深入的了解,但这也与我们的技能水平,我们的知识息息相关(您如何决定优缺点?好吧,请参阅下文) ...),以及我们对我们认为软件所知的所有假设...:

  • 确实有
  • 实际上需要做的
  • 最终将需要做,
  • 以及效果如何。

真的需要优化吗?

综上所述,为什么首先要“优化”?他们说过早的优化是万恶之源,如果您看到未记录的,看似经过优化的代码,通常可以假定它可能不遵循优化规则,并不需要太多的优化工作,而这仅仅是通常情况下,开发人员的狂妄自大。然而,也许现在只是您的讲话。

如果可以,在什么范围内可以接受?如果有需要,则此限制存在,并为您提供了改进的空间,也为您决定放弃它的强硬路线。

另外,提防看不见的特征。很有可能,此代码的“可扩展”版本也将在运行时增加内存,并为可执行文件提供更大的静态内存。此类OO闪闪发亮的功能会带来不直观的成本,它们可能会影响您的程序以及应在其上运行的环境。

测量,测量,测量

就像Google员工一样,这全都与数据有关!如果可以用数据备份它,那么这是必要的。

有一个不那么古老的传说,在开发上每花费1 美元,测试之后将至少花费1 美元,在支持上至少花费1美元(但实际上,它还要多得多)。

变更影响很多方面:

  • 您可能需要生成一个新的版本;
  • 您应该编写新的单元测试(如果没有,肯定是这样,并且您可扩展性更高的体系结构可能会留出更多的空间,因为您有更多的漏洞可以解决这些问题);
  • 您应该编写新的性能测试(以确保将来可以保持稳定,并查看瓶颈在哪里),而这样做很棘手
  • 您需要对其进行记录(并且扩展性越强,就意味着更多的细节空间);
  • 您(或其他人)将需要在质量检查中进行广泛的重新测试;
  • 代码几乎(几乎)没有错误,因此您需要对其进行支持。

因此,您不仅需要在这里衡量硬件资源消耗(执行速度或内存占用),还需要团队资源消耗。都需要预测两者以定义目标目标,并根据开发情况对其进行度量,说明和调整。

对于您的经理来说,这意味着将其适应当前的开发计划,因此请就此进行交流,不要陷入愤怒的牛仔/潜艇/黑手党编码中。


一般来说...

对,但是...

别误会我的意思,总的来说,我会支持您提出的建议,并且我经常提倡这样做。但是,您需要了解长期成本。

在理想环境中,这是正确的解决方案:

  • 随着时间的流逝,计算机硬件会越来越好,
  • 随着时间的推移,编译器和运行时平台会变得越来越好,
  • 您将获得接近完美,干净,可维护和易读的代码。

在实践中:

  • 你可能会变得更糟

    您需要更多的眼球才能看到它,并且使其复杂化的程度也就越高。

  • 你无法预测未来

    您无法绝对确定是否需要它,即使您需要的“扩展”在旧形式中实施起来会更容易,更快捷,以及是否需要对其进行超级优化,也无法绝对确定。 。

  • 从管理层的角度来看,它代表着无直接收益的巨大成本。

使其成为流程的一部分

您在这里提到这是一个很小的变化,并且您会想到一些特定的问题。在这种情况下,我通常说这还可以,但是我们大多数人还都有一些个人故事,包括微小的变化,几乎是外科手术的修改,最终变成了维护噩梦,并且由于乔·程序员没有看到一个截止日期而几乎错过或爆炸了最后期限代码背后的原因并触及了不该有的东西。

如果您有处理此类决定的流程,则可以从他们的个人优势中脱颖而出:

  • 如果您正确地进行测试,则可以更快地了解到发生故障的情况,
  • 如果对它们进行衡量,就会知道它们是否有所改善,
  • 如果您对其进行审查,就会知道它是否会让人们失望。

测试覆盖率,分析和数据收集都是棘手的

但是,当然,您的测试代码和指标可能会遇到与实际代码一样要避免的问题:您是否测试了正确的东西,它们在将来是否正确,并且您测量了正确的东西东西?

不过,通常来说,您测试(直到一定的限制)和度量的次数越多,您收集的数据就越多,您就越安全。不好的类比时间:将其视为驾驶(或一般生活):如果汽车抛锚了您或某人决定今天开车与您自己开车撞死自己,您可以成为世界上最好的驾驶员技能可能还不够。既有环境因素也可能打击您,人为错误也很重要。

代码审查是开发团队的走廊测试

我认为最后一部分是关键:进行代码审查。如果仅使它们改进,您将不会知道其价值。代码审查是我们的“走廊测试”:请遵循雷蒙德(Linus)法则的版本,以检测错误,检测过度设计和其他反模式,并确保代码与您团队的能力保持一致。如果没有其他人,但是您可以理解和维护,那么拥有“最佳”代码毫无意义,这对于密码优化和6层深度体系结构设计都是如此。

作为结束语,请记住:

众所周知,调试的难度是编写程序的两倍。因此,如果您在编写时尽可能聪明,那么将如何调试它?- 布赖恩·克尼根Brian Kernighan)


“如果没有解决,请不要解决”与重构相反。不管是否可行,如果需要维护,都需要更改。
宫本晃

@MiyamotoAkira:这是两速的事情。如果它不破,但可以接受的,不太可能看到的支持,它可能是可以接受的息事宁人,而不是将其引入潜在的新的bug或花费的开发时间。这是关于评估重构的短期和长期收益的全部内容。没有明确的答案,它确实需要一些评估。
haylem 2012年

同意 我想我不喜欢这句话(及其背后的哲学),因为我将重构视为默认选项,并且只有当看起来花费的时间太长或太困难时,才会/应该决定不这样做。去吧。请注意,我一直被那些没有改变事情的人所烦恼,尽管工作,但显然是必须维护或扩展它们的错误解决方案。
宫本晃

@MiyamotoAkira:简短的句子和意见陈述不能表达太多。我想它们应该是在您的脸上,并且可以在侧面进行开发。我本人非常乐意尽可能多地审查和修改代码,即使经常没有很大的安全网或没有太多理由也是如此。如果脏了,请清洁。但是,类似地,我也被烧了好几次。并且仍然会被烧毁。只要不是三级学位,我就不在乎,到目前为止,长期利益总是短期燃烧。
haylem 2012年

8

通常,您应该首先关注可读性,然后再关注性能。在大多数情况下,这些性能优化都可以忽略不计,但是维护成本可能很高。

当然,所有“小”事情都应更改为清晰起见,因为正如您所指出的,无论如何大多数都将由编译器进行优化。

至于更大的优化,可能有可能这些优化实际上对于达到合理的性能至关重要(尽管这种情况通常并不令人惊讶)。我将进行更改,然后在更改前后对代码进行概要分析。如果新代码存在严重的性能问题,则可以始终回滚到优化版本,如果没有,则可以坚持使用更干净的代码版本。

一次仅更改代码的一部分,并查看每一轮重构后它如何影响性能。


8

这取决于为什么要对代码进行优化以及对其进行更改的影响以及代码对整体性能的影响。它还应取决于您是否具有加载测试更改的好方法。

更改之前和之后以及可能在类似于生产中看到的负载下进行概要分析时,请勿进行此更改。这意味着在只有一个用户使用系统时,不要在开发人员机器上使用一小部分数据或进行测试。

如果优化是最近进行的,则您可以与开发人员进行交谈,并准确找出问题所在以及优化之前应用程序的运行速度。这可以告诉您很多关于进行优化是否值得以及需要进行优化的条件的信息(例如,如果要测试变更,那么直到9月或10月,涵盖整整一年的报告可能并不会变慢。在2月份,速度可能尚未明显且测试无效)。

如果优化过时,则更新的方法可能会更快,更易读。

最终,这是您老板的一个问题。重构已优化的内容并确保所做的更改不会影响最终结果,并且与旧方法相比,其性能好或至少可以接受,这是很费时间的。他可能希望您将时间花在其他方面,而不是执行高风险的任务以节省几分钟的编码时间。或者他可能同意该代码难以理解,需要经常干预,并且现在可以使用更好的方法。


6

如果性能分析表明不需要优化(不在关键部分中),或者运行时更糟(由于不良的过早优化),那么请确保将其替换为易于维护的可读代码

还要确保代码在适当的测试下的行为相同


5

从业务角度考虑它。变更的成本是多少?您需要花多少时间进行更改,从长远来看,通过使代码更易于扩展或维护,可以节省多少时间?现在,给该时间附加一个价格标签,并将其与由于降低性能而损失的钱进行比较。也许您需要添加或升级服务器以弥补性能损失。也许该产品不再符合要求,无法再销售。也许没有损失。也许更改可以提高鲁棒性并节省其他时间。现在做出决定。

附带说明,在某些情况下,可以保留一个片段的两个版本。您可以编写一个生成随机输入值的测试,然后使用其他版本来验证结果。使用“聪明的”解决方案来检查一种完全可以理解且显然正确的解决方案的结果,从而可以放心(但无证据)新解决方案与旧解决方案等效。或者反过来,用详细代码检查棘手代码的结果,从而以明确的方式记录黑客的意图。


4

基本上,您是在问重构是否值得。答案肯定是肯定的。

但...

...您需要仔细做。您需要对要重构的任何代码进行可靠的单元,集成,功能和性能测试。您需要确信他们确实在测试所有必需的功能。您需要能够轻松且反复地运行它们的能力。一旦有了这些,就应该能够用包含等效功能的新组件替换组件。

马丁·福勒(Martin Fowler)就此撰写了这本书


3

您不应无正当理由更改工作的生产代码。除非您无法进行重构,否则“重构”并不是一个足够好的理由。即使您所做的是修复困难代码本身中的错误,您也应该花些时间来理解它,并进行最小的更改。如果代码很难理解,那么您将无法完全理解它,因此所做的任何更改都会产生不可预测的副作用-换句话说,就是错误。变化越大,您造成麻烦的可能性就越大。

会有一个例外:如果难以理解的代码具有完整的单元测试集,则可以对其进行重构。由于我从未见过或听说过完整的单元测试无法理解的代码,因此您首先编写单元测试,获得必要人员的同意,即这些单元测试实际上代表了代码应该执行的工作,然后进行代码更改。我已经做了一两次。这是一个脖子上的痛苦,而且非常昂贵,但最终确实会产生良好的效果。


3

如果只是一小段代码以一种难以理解的方式完成了相对简单的事情,那么我将在扩展注释和/或未使用的替代实现中转移“快速理解”,例如

#ifdef READABLE_ALT_IMPLEMENTATION

   double x=0;
   for(double n: summands)
     x += n;
   return x;

#else

   auto subsum = [&](int lb, int rb){
          double x=0;
          while(lb<rb)
            x += summands[lb++];
          return x;
        };
   double x_fin=0;
   for(double nsm: par_eval( subsum
                           , partitions(n_threads, 0, summands.size()) ) )
     x_fin += nsm;
   return x_fin;

#endif

3

答案是,不失一般性,是的。当您看到难以阅读的代码时,请务必添加现代代码,并在大多数情况下删除不良代码。我使用以下过程:

  1. 查找性能测试和支持的性能分析信息。如果没有性能测试,则可以在没有证据的情况下断言没有证据的主张。断言您的现代代码更快,并删除了旧代码。如果有人(甚至是您自己)争论,请他们编写配置文件代码以证明哪个更快。
  2. 如果存在概要代码,则无论如何都要编写现代代码。命名为<function>_clean()。然后,将您的代码与错误代码“竞争”。如果您的代码更好,请删除旧代码。
  3. 如果旧代码更快,则无论如何都要保留现代代码。它可以很好地说明其他代码的作用,并且由于存在“种族”代码,因此您可以继续运行它以记录性能特征和两条路径之间的差异。您还可以对代码行为的差异进行单元测试。重要的是,有保证的是,现代代码将有一天击败“优化”代码。然后,您可以删除错误的代码。

QED。


3

如果我能在死之前就向世界传授一件事(关于软件),那我会告诉人们“性能与X”是一个错误的困境。

重构通常被称为可读性和可靠性的福音,但它同样可以轻松地支持优化。当您通过一系列重构来处理性能改进时,您既可以遵守Campsite规则,又可以使应用程序运行得更快。至少在我看来,这样做实际上是道德上的责任。

例如,此问题的作者遇到了一段疯狂的代码。如果这个人在阅读我的代码,他们会发现疯狂的部分是3-4行。它本身在方法中,并且方法名称和描述指示该方法在做什么。该方法将包含2-6行内联注释,描述疯狂代码如何获得正确答案(尽管其外观存在问题)。

通过这种方式,您可以随意交换该方法的实现。确实,这可能就是我最初编写疯狂版本的方式。欢迎您尝试,或至少询问其他选择。大多数时候,您会发现幼稚的实现明显更糟(通常我只为2-10倍的改进而烦恼),但是编译器和库总是在变化,谁知道您今天会发现什么在什么时候不可用函数是写的吗?


在许多情况下,提高效率的主要关键是使代码以可以高效完成的方式完成尽可能多的工作。.NET让我烦恼的一件事是,没有有效的机制例如将一个集合的一部分复制到另一个集合。大多数集合将大量连续的项目(如果不是全部)存储在数组中,因此例如从50,000个项目的列表中复制最后5,000个项目应分解为几个大容量复制操作(如果不仅仅是一个)再加上其他一些操作每个步骤最多只能执行几次。
2014年

不幸的是,即使在应该有可能有效地执行此类操作的情况下,通常也有必要使“庞大”的循环运行5,000次迭代(在某些情况下为45,000!)。如果可以将操作简化为批量阵列副本之类的东西,那么可以将这些操作优化到极高的程度,从而产生可观的收益。如果每个循环迭代都需要执行许多步骤,那么很难特别优化这些步骤。
2014年

2

触摸它可能不是一个好主意-如果出于性能原因以这种方式编写代码,则意味着对其进行更改可能会带回以前解决的性能问题。

如果决定改变的事情,更可读和可扩展:在你做出改变,基准的旧代码下沉重的负荷。如果您能找到描述此奇特代码应解决的性能问题的旧文档或故障单,那就更好了。然后,在您进行更改后,再次运行性能测试。如果差别不大或仍在可接受的参数范围内,那可能就可以了。

有时可能会发生这样的情况:当系统的其他部分发生更改时,这种性能优化的代码不再需要进行如此繁琐的优化,但是如果没有严格的测试,就无法确定这一点。


1
与我合作的一个人现在喜欢优化用户每月点击一次的区域中的内容(如果有的话)。这很花时间,而且很少会引起其他问题,因为他喜欢编码和提交,并让QA或其他下游功能进行实际测试。:/说句公道话,他一般都很敏捷,敏捷和准确,但是这些一分钱事前的“优化”只会让团队的其他成员感到困难,而他们的永久死亡将是一件好事。
DaveE 2012年

@DaveE:是否因为实际性能问题而应用了这些优化?还是仅仅因为他能做到,开发人员就这样做了我猜想,如果您知道优化不会产生影响,则可以用更具可读性的代码安全地替换它们,但是我只相信系统专家可以做到这一点。
FrustratedWithFormsDesigner 2012年

他们之所以完成是因为他可以。实际上,他通常节省一些周期,但是当用户与程序元素的交互花费一些秒(15到300 ish)时,为了追求“效率”而将运行时间缩短了十分之一秒是很愚蠢的。特别是当跟随他的人们不得不花时间了解他的所作所为时。这是最初在16年前构建的PowerBuilder应用程序,因此从事物的起源来看,这种思维方式也许是可以理解的,但是他拒绝将其思维方式更新为当前的现实。
DaveE 2012年

@DaveE:我认为与您一起工作的人比您更同意。如果绝对没有充分的理由不允许我修复缓慢的内容,我会发疯的。如果我看到一行C ++反复使用+运算符来组装一个字符串,或者看到每次由于有人忘记设置标志而每次在循环中打开并读取/ dev / urandom的代码,那么我都将其修复。通过对此狂热,我设法保持了速度,而其他人却一次让它滑了1微秒。
Zan Lynx 2012年

1
好吧,我们将不得不同意不同意。花一个小时来更改某些内容,以在运行时节省实际上只需要偶尔执行一次的功能,而将代码保留在其他开发人员的脑子里的形状是不正确的。如果这些功能在应用程序的高压力部分中反复执行,那就很好。但这不是我要描述的情况。这确实是无用的代码查询,其原因无非是说“我使UserX每周做一次这样的事情的速度要小一些”。同时,我们要付出需要做的工作。
DaveE 2012年

2

这里的问题是将“优化”与可读性和可扩展性区分开来,我们作为用户看到的是优化代码,而编译器看到的是优化是两件事。您要更改的代码可能根本不是瓶颈,因此,即使代码“精简”,也不需要对其进行“优化”。或者,如果代码足够陈旧,则编译器可能会对内置代码进行了优化,从而使使用较新的简单内置结构比旧代码具有同等或更有效的效率。

而且,“精益”,不可读的代码并不总是得到优化。

我以前的想法是,聪明/精简的代码是好的代码,但是有时候利用模糊的语言规则会伤害而不是帮助代码创建,但在尝试进行嵌入式开发时,我经常被咬伤,而不是在任何嵌入式工作中之所以要聪明,是因为编译器使您的聪明代码变成嵌入式硬件完全无法使用的东西。


2

我绝不会用可读代码代替优化代码,因为我不会牺牲性能,并且我会选择在每个部分都使用适当的注释,以便任何人都可以理解该优化部分中实现的逻辑,这将解决这两个问题。

因此,代码将被优化+适当的注释也将使其可读。

注意:您可以在适当的注释帮助下使优化代码可读,但不能使可读代码成为优化代码。


我将对这种方法感到厌倦,因为它只需要一个人编辑代码而忘记保持注释同步。突然,以后每次评审会走开想它执行X,而实际做Y.
约翰·d

2

这是查看简单代码和优化代码之间区别的示例:https : //stackoverflow.com/a/11227902/1396264

在答案的结尾,他只是替换了:

if (data[c] >= 128)
    sum += data[c];

与:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

公平地说,我不知道用if语句替换了什么,但是正如回答者所说的那样,它的按位运算给出了相同的结果(我只是相信他的话)

执行时间不到原始时间的四分之一 (11.54秒vs 2.5秒)


1

这里的主要问题是:是否需要优化?

如果是,则无法用较慢,更易读的代码替换它。您将需要添加注释等以使其更具可读性。

如果不必对代码进行优化,则不应对其进行优化(以至影响可读性),您可以对其进行重构以使其更具可读性。

但是-在开始进行更改之前,请确保您确切了解代码的作用以及如何对其进行全面测试。这包括峰值使用率等。如果不必组成一组测试用例并在前后前后运行它们,则您没有时间进行重构。


1

这是我做事的方式:首先,我使它以可读代码运行,然后对其进行优化。我保留原始资料并记录我的优化步骤。

然后,当我需要添加功能时,请返回可读代码,添加功能并按照我记录的优化步骤进行操作。因为您已记录,所以使用新功能重新优化代码确实非常快捷。


0

IMHO的可读性比优化的代码更重要,因为在大多数情况下,微优化是不值得的。

关于无意义的微优化的文章

作为我们大多数人,我很疲倦地阅读有关无意义的微优化的博客文章,例如用echo代替print,用$ i ++代替++ $ i或用单引号引起双引号。为什么?因为99.999999%的时间是无关紧要的。

“ print”比“ echo”使用更多的操作码,因为它实际上返回了一些东西。我们可以得出结论,回声比打印要快。但是,一个操作码不花钱,真的不花钱。

我已经尝试过全新的WordPress安装。该脚本在我的笔记本电脑上以“总线错误”结束之前停止运行,但操作码数量已超过230万。说够了。


0

优化是相对的。例如:

考虑一班有很多BOOL成员的班级:

// no nitpicking over BOOL vs bool allowed
class Pear {
 ...
 BOOL m_peeled;
 BOOL m_sliced;
 BOOL m_pitted;
 BOOL m_rotten;
 ...
};

您可能会想将BOOL字段转换为位字段:

class Pear {
 ...
 BOOL m_peeled:1;
 BOOL m_sliced:1;
 BOOL m_pitted:1;
 BOOL m_rotten:1;
 ...
};

由于BOOL的类型定义为INT(在Windows平台上是32位有符号整数),因此需要16个字节并将它们打包为1个字节。节省93%!谁会抱怨呢?

这个假设:

由于BOOL的类型定义为INT(在Windows平台上是32位有符号整数),因此需要16个字节并将它们打包为1个字节。节省93%!谁会抱怨呢?

导致:

将BOOL转换为一个单位字段可以节省3个字节的数据,但是当为该成员分配一个非恒定值时,则要花费8个字节的代码。同样,提取值也变得更加昂贵。

曾经是

 push [ebx+01Ch]      ; m_sliced
 call _Something@4    ; Something(m_sliced);

变成

 mov  ecx, [ebx+01Ch] ; load bitfield value
 shl  ecx, 30         ; put bit at top
 sar  ecx, 31         ; move down and sign extend
 push ecx
 call _Something@4    ; Something(m_sliced);

位域版本大9个字节。

让我们坐下来做一些算术。假设每个位域字段在您的代码中均被访问了六次,写了三遍,读了三遍。代码增长的成本约为100个字节。由于优化器可能能够利用寄存器中已经存在的值来进行某些操作,因此它不会精确地为102个字节,并且附加指令在降低寄存器灵活性方面可能具有隐藏的成本。实际的差异可能更大,也可能更小,但对于封底计算,我们将其称为100。同时,每个类节省的内存为15字节。因此,收支平衡点是7。如果您的程序创建的此类的实例少于七个,则代码成本超过了节省的数据量:内存优化是内存去优化。

参考文献

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.