我目前正在为一个客户工作,他们因为“性能原因”而对更改糟糕的不可测试和不可维护的代码感到震惊。很明显,有很多误解普遍存在,原因不被理解,而只是盲目相信。
我遇到的一种这样的反模式是需要将尽可能多的类标记为密封的内部......
*重新编辑:我认为将所有内容标记为内部密封(在 C# 中)作为过早的优化。*
我想知道人们可能知道或遇到的其他一些性能反模式是什么?
我目前正在为一个客户工作,他们因为“性能原因”而对更改糟糕的不可测试和不可维护的代码感到震惊。很明显,有很多误解普遍存在,原因不被理解,而只是盲目相信。
我遇到的一种这样的反模式是需要将尽可能多的类标记为密封的内部......
*重新编辑:我认为将所有内容标记为内部密封(在 C# 中)作为过早的优化。*
我想知道人们可能知道或遇到的其他一些性能反模式是什么?
我遇到的最大的性能反模式是:
收集性能数据将显示某种技术是否成功。不这样做会导致非常无用的活动,因为当什么都没有改变时,有人会有提高性能的“感觉”。
房间里的大象: 专注于实现级别的微优化,而不是更好的算法。
可变重用。
我过去一直这样做,因为我认为我在声明中节省了几个周期并降低了内存占用。与它使代码调试的不规则性相比,这些节省的价值微不足道,尤其是如果我最终移动了一个代码块并且对起始值的假设发生了变化。
过早的性能优化浮现在脑海中。我倾向于不惜一切代价避免性能优化,当我决定我确实需要它们时,我会将问题转给我的同事几轮,以确保我们将模糊...嗯优化放在正确的位置。
我遇到的一个问题是将硬件投入到严重损坏的代码中,以使其足够快,这与 Rulas 评论中提到的 Jeff Atwood 的文章相反。我不是在谈论通过在更快的硬件上运行使用基本的正确算法来加速排序与使用优化算法之间的区别。当标准库中有 O(n log n) 算法时,我说的是使用不明显正确的自制 O(n^3) 算法。还有诸如手动编码例程之类的东西,因为程序员不知道标准库中有什么。那是非常令人沮丧的。
使用设计模式只是为了使用它们。
使用#defines 而不是函数来避免函数调用的惩罚。我已经看到代码的定义扩展结果会生成巨大且非常慢的代码。当然也无法调试。内联函数是执行此操作的方法,但也应谨慎使用它们。
我见过代码,其中独立测试已被转换为可在 switch 语句中使用的单词中的位。Switch 可以非常快,但是当人们将一系列独立测试转换为位掩码并开始编写一些 256 个优化的特殊情况时,他们最好有一个非常好的基准来证明这会带来性能提升。从维护的角度来看,这确实很痛苦,并且独立处理不同的测试会使代码更小,这对性能也很重要。
缺乏清晰的程序结构是其中最大的代码罪过。被认为很快的复杂逻辑几乎从来没有。
利用你的编程语言。诸如使用异常处理而不是 if/else 之类的事情只是因为在 PLSnakish 1.4 中它更快。你猜怎么了?有可能它根本没有更快,两年后维护您的代码的人会非常生气,因为您混淆了代码并使其运行速度慢得多,因为在 PLSnakish 1.8 中,语言维护人员修复了问题,现在 if/else比使用异常处理技巧快 10 倍。使用您的编程语言和框架!
在编写代码时不要重构或优化。在完成之前不要尝试优化代码,这一点非常重要。
Julian Birch 曾经告诉我:
“是的,但是实际运行该应用程序需要多少年才能弥补开发人员花费的时间?”
他指的是每次交易期间通过需要一定时间来实施的优化所节省的累计时间。
老圣人的智慧之言……在考虑进行时髦的优化时,我经常想到这个建议。您可以通过考虑开发人员花费了多少时间来处理处于当前状态的代码与用户节省了多少时间来进一步扩展相同的概念。如果您愿意,您甚至可以按开发人员与用户的小时费率来衡量时间。
当然,有时它是无法衡量的,例如,如果电子商务应用程序需要多 1 秒的时间来响应,那么您将在 1 秒内因用户感到无聊而损失一些小部分资金。为了弥补这一秒,您需要实现和维护优化的代码。优化对毛利润产生积极影响,对净利润产生消极影响,因此更难以平衡。您可以尝试-具有良好的统计数据。
一次更改多个变量。这让我非常疯狂!当不止一件事发生变化时,如何确定更改对系统的影响?
与此相关的是,进行观察无法保证的更改。如果进程不受 CPU 限制,为什么要添加更快/更多的 CPU?
一般解决方案。
仅仅因为给定的模式/技术在一种情况下表现更好并不意味着它在另一种情况下表现更好。
.Net 中过度使用 StringBuilder 就是一个常见的例子。
Michael A Jackson 给出了优化性能的两条规则:
如果人们担心性能,告诉他们让它成为现实——什么是好的性能,你如何测试它?然后,如果您的代码没有达到他们的标准,至少这是代码编写者和应用程序用户同意的事情。
如果人们担心重写僵化代码的非性能成本(例如,时间槽),那么请提出您的估计并证明它可以在时间表中完成。假设可以。
一些开发人员认为,一个快速但不正确的解决方案有时比一个缓慢但正确的解决方案更可取。所以他们会忽略生产中“永远不会发生”或“无关紧要”的各种边界条件或情况。
这从来都不是一个好主意。解决方案总是需要“正确”。
您可能需要根据情况调整“正确”的定义。重要的是您知道/准确地定义您希望任何条件下的结果,并且代码给出了这些结果。
有一次我有一个前客户打电话给我,询问我对加快他们的应用程序的任何建议。
他似乎希望我说“检查 X,然后检查 Y,然后检查 Z”之类的话,换句话说,提供专家猜测。
我回答说你必须诊断问题。我的猜测可能比别人的错误少,但他们仍然是错误的,因此令人失望。
我不认为他明白。
我相信这是一个普遍的神话,即“接近金属”的超级精简代码比优雅的域模型更高效。
这显然被 DirectX 的创建者/首席开发人员揭穿了,他们用 C# 重新编写了 c++ 版本,并进行了大量改进。[需要来源]
当您提前知道数组应该有多大并且可以预先分配它时,使用(例如)C++ STL 中的 push_back()、D 中的 ~= 等附加到数组。