有时,您会遇到必须扩展/改进某些现有代码的情况。您会看到旧代码非常精简,但也很难扩展,而且花了一些时间阅读。 >前段时间我喜欢精益方法,但现在看来,最好牺牲很多优化,而采用更高的抽象,更好的接口和更易读,可扩展的代码。

编译器似乎也越来越好,所以像struct abc = {}这样的东西被悄悄地变成了memsetshared_ptr几乎产生了与原始指针扭曲相同的代码,模板工作得很好,因为它们产生了超瘦代码,等等。

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

如果您必须以某种方式触摸一小段代码,请更改此类代码?

评论

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

通过一些注释可以提高可读性吗?

令人担忧的是,将OOP规范化为“现代代码”

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

优化代码是指实际的优化代码还是所谓的优化代码?

#1 楼

在哪里?


在Google规模的网站的首页上是不可接受的。尽可能快地进行操作。
在一个人每年使用一次的应用程序的一部分中,为了获得代码的可读性而牺牲性能是完全可以接受的。

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

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

评论


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

– Tacroy
2012年6月11日19:27

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

–Gor机器人
2012年6月11日19:35

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

–克里斯K
2012年6月11日20:31

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

–路卡
2012年6月11日23:08

#2 楼

通常,没有。

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

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

当然,配置文件,配置文件,配置文件,尤其是在关键路径区域的情况下。

评论


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

– Haylem
2012年6月12日下午3:50

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

–德米安·布莱希特(Demian Brecht)
2012年6月12日13:56

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

–rwong
13-10-23在4:25



#3 楼

简而言之:它取决于




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


是否有一个是具体的收益,是立即的还是长期的?
这个收益是仅出于维护性还是真正的建筑性? />

为什么?
您需要达到什么目标增益?





详细信息
/>
您需要清理干净的东西吗?

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


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


它可能更可扩展,但可能不是更容易扩展,
可能不容易理解,
最后,但绝对不是最不重要的:您可能会降低整个代码的速度。简单的方法还是整洁的架构方法?答案不一定是绝对的,如以下其余部分所述。

您将不需要它

YAGNI原理与其他问题并不完全正交,但它会帮助您问一个问题:您是否需要它?

除了使外观更具可维护性之外,更复杂的体系结构真的对您有好处吗?

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

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

这很自然让我们在阅读过程中试图理解代码时,甚至在不知不觉中“改进”代码甚至只是触摸代码。这是一件好事,因为这意味着我们会固执己见,并试图对内部构造有更深入的了解,但这也与我们的技能水平,我们的知识(您如何决定优劣之道?好吧,请参阅下文)联系在一起。 ...),以及我们对我们认为自己了解该软件的一切假设...:


实际上需要做,
实际上需要做,
最终将需要做的事情,
做得如何。

真的需要优化吗?

所有这些,为什么首先要“优化”?他们说过早的优化是万恶之源,如果您看到未记录的,看似经过优化的代码,通常可以假定它可能不遵循优化规则,因此不需要太多的优化工作,而仅仅是常见的开发人员狂妄自大。但是,也许现在只是您的话题。如果有需要,此限制将存在,并为您提供改进的空间,也为您决定放弃它提供了强硬路线。很有可能,此代码的“可扩展”版本也将在运行时增加更多内存,并为可执行文件提供更大的静态内存占用。闪亮的OO功能会带来诸如此类的不直观的成本,它们可能会影响您的程序以及应该在其上运行的环境。就像现在的Google员工一样,一切都与数据有关!如果您可以用数据进行备份,那么这是有必要的。

这个故事还不那么古老,以至于每花1美元在开发上,至少要花费1美元进行测试,再加上至少1美元的支持费用。 (但实际上,还有很多)。

变化会影响很多事情:


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

所以,这不仅仅是硬件资源您需要消耗的(执行速度或内存占用) d在这里进行衡量,这也是团队资源消耗。都需要预测两者,以定义目标目标,根据开发情况对其进行度量,说明和调整。关于它,不要陷入愤怒的牛仔/潜水艇/黑警的编码中。


一般...

是的,但是...

不要误会我的意思,总的来说,我会支持你的建议,我经常主张这样做。但是,您需要意识到长期成本。

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



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


您可能会变得更糟

您需要更多的眼球来观察它,并且它变得越复杂,您就需要越多的眼球。不能绝对确定您是否需要它,即使您需要的“扩展”原本可以更容易,更快速地以旧形式实现,又不能对它们进行超级优化,也无法绝对确定。

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

使它成为流程的一部分

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

如果您有处理此类决策的流程,则可以从他们的个人优势中脱身: >
如果您正确地测试了东西,就会更快地知道它们是否损坏,
如果您对其进行测量,就会知道它们是否有所改善,
如果您对其进行审查,您将知道它是否会让人们失望。

测试覆盖率,性能分析和数据收集都是棘手的事情正在尝试避免使用您的实际代码:您是否测试了正确的东西,并且它们是否适合将来的未来,您是否测量了正确的东西?

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

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

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


每个人都知道调试的难度是编写程序的两倍。因此,如果您在编写代码时尽可能聪明,那么将如何调试它? -Brian Kernighan


评论


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

–宫本晃
2012年7月4日11:56



@MiyamotoAkira:这是两速的事情。如果它没有损坏但可以接受,并且不太可能获得支持,则可以不理会它,而不要引入潜在的新错误或花费开发时间在上面。这是关于评估重构的短期和长期收益的全部内容。没有明确的答案,它确实需要一些评估。

– Haylem
2012年7月4日在12:18

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

–宫本晃
2012年7月4日12:30



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

– Haylem
2012年7月4日在12:41

#4 楼

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

当然,所有“小”事情都应更改为清晰,因为正如您所指出的那样,其中的任何一个都会通过编译器进行优化。

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

在同一时间只更改部分代码即可。时间,看看每轮重构后它如何影响性能。

#5 楼

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

在更改之前和之后以及可能在类似于生产时会看到的负载的情况下进行概要分析时,请勿进行此更改。这意味着在只有一个用户使用系统的情况下,不要在开发人员的机器上使用很小的数据子集或进行测试。到底是什么问题,以及优化之前应用程序的速度如何。这可以告诉您很多关于进行优化是否值得以及需要进行优化的条件的信息(例如,如果要测试更改,那么直到9月或10月,覆盖整整一年的报告可能并不会变慢。在2月份,速度可能尚未明显且测试无效)。

如果优化过旧,则更新的方法甚至可能更快,更易读。

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

#6 楼

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

还要确保代码在适当的测试下表现相同

#7 楼

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

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

#8 楼

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

但是...

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

Martin Fowler就此撰写了这本书。

#9 楼

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

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

#10 楼

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

#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


#11 楼

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


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

QED。

#12 楼

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

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

例如,这个问题的作者遇到了一段疯狂的代码。如果这个人在阅读我的代码,他们会发现疯狂的部分是3-4行。它本身在方法中,并且方法名称和描述指示该方法在做什么。该方法将包含2-6行内联注释,描述了疯狂代码如何获得正确答案(尽管其外观存在问题)。你喜欢。确实,这可能就是我最初编写疯狂版本的方式。欢迎您尝试,或至少询问其他选择。大多数时候,您会发现幼稚的实现明显更糟(通常我只为2-10倍的改进而烦恼),但是编译器和库总是在变化,谁知道您今天会发现什么在什么时候不可用函数是写的吗?

评论


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

–超级猫
14年5月13日在3:59



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

–超级猫
2014年5月13日下午4:03

#13 楼

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

如果您决定更改某些内容以使其更具可读性和可扩展性:在进行更改之前,请对较重的代码进行基准测试。如果您能找到描述此奇特代码应解决的性能问题的旧文档或故障单,那就更好了。然后,在进行更改后,再次运行性能测试。如果差异不大或仍在可接受的参数范围内,那可能就可以。

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

评论


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

– DaveE
2012年6月11日17:12

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

– FrustratedWithFormsDesigner
2012年6月11日17:31



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

– DaveE
2012年6月11日17:43

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

–赞·山猫
2012年6月11日23:29

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

– DaveE
2012年6月11日23:58



#14 楼

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

而且“精益”的可读代码并不总是得到优化。晦涩的语言规则会伤害而不是对代码创建没有帮助,在尝试变得聪明的时候,我经常在任何嵌入式工作中都比不做,因为编译器使您的聪明代码变成嵌入式硬件完全无法使用的东西。 >

#15 楼

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

注意:您可以在适当的注释的帮助下使优化的代码可读,但无法使代码可读编写一个优化的代码。

评论


我会对这种方法感到厌倦,因为它只需要一个人编辑代码而忘记保持注释同步。突然,以后的每条评论都会以为它在实际执行Y的同时执行X而走开。

–约翰D
2012年8月13日在15:36

#16 楼

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

在他刚刚替换的答案的结尾:

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


:回答者说,它的一些按位运算给出了相同的结果(我只是相信他的话)。

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

#17 楼

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

如果不能优化,就不能用更慢,更易读的代码替换它。您将需要添加注释等以使其更具可读性。

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

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

#18 楼

这是我做事的方式:首先,我使它以可读代码运行,然后对其进行优化。我保留了原始资料并记录了我的优化步骤。因为您已经记录了文档,所以使用新功能重新优化代码确实非常快捷。

#19 楼

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

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


作为我们大多数人,我厌倦了阅读有关无意义的微优化的博客文章
-优化,例如用echo替换print,用$ i ++替换++ $ i或用单引号将
括起来。为什么?因为99.999999%的时间是不相关的。

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

我尝试了全新的WordPress安装。该脚本在我的笔记本电脑上以“ Bus Error”结束之前终止
,但是
操作码的数量已超过230万。说够了。


#20 楼

优化是相对的。例如:


考虑带有一堆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个字节并将其打包为一个字节。节省93%!谁会对此表示抱怨? 32位整数),它将占用16个字节并将其打包为1个字节。节省93%!谁会抱怨呢?


导致:当为成员分配一个非恒定值时,这将是八个字节的代码。同样,提取值也变得更加昂贵。

以前是


 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。如果您的程序创建的此类实例少于七个,则代码成本超过了节省的数据量:您的内存优化是内存的非优化。


参考文献


布尔值集合的位域的成本效益分析–旧的新事物
位域缺陷-硬件错误| pagetable.com