流行文章告诉我foo比bar快怎么办?

译者注:我还认为写这篇文章的时间是“什么更快-双引号或单引号?” 又过了十年。 但是这里有一篇类似的文章(“什么性能技巧真正起作用”)最近在Reddit上获得了相对较高的评价,甚至进入了Habré上的PHP摘要 因此,我决定通过对这些和类似“测试”的严格分析来翻译本文。


有很多文章(甚至整个站点)都专门介绍了各种测试,它们比较了各种语法结构的性能,并在此基础上指出,一个比另一个要快。


主要问题


这样的测试是错误的,原因有很多,从提出问题到实现错误。 但最重要的是-这样的测试毫无意义,同时有害。


  • 它们没有意义,因为它们没有实际价值。 使用此类文章中提供的方法从未加速过任何实际项目。 仅仅因为语法上的差异与性能无关,而是数据处理。
  • 它们是有害的,因为它们会导致出现最疯狂的迷信,并且甚至更糟的是,鼓励毫无戒心的读者编写错误的代码,以为他们“对其进行了优化”。

这应该足以解决问题。 但是,即使您接受游戏规则并假装这些“测试”至少具有某种意义,事实证明,将其结果简化为证明测试人员缺乏知识和缺乏经验的事实。


单人与双人


以臭名昭著的报价,“单对双”。 当然,没有报价会更快。 首先,有一个操作码缓存之类的东西,它将解析PHP脚本的结果存储在缓存中。 在这种情况下,PHP代码以操作码格式保存,其中相同的字符串文字存储为绝对相同的实体,而不管PHP脚本中使用了什么引号。 这意味着即使在性能上也没有理论上的差异。


但是,即使我们不使用操作码缓存(尽管应该这样做,如果我们的任务是真正提高性能),我们也会发现解析代码的差异是如此之小(几个条件转换比较单字节字符,实际上是几个处理器指令),这绝对是绝对的。无法检测。 这意味着所获得的任何结果都只会证明测试环境中存在问题。 PHP核心开发人员Nikita Popov 撰写了一篇非常详细的文章,《 反驳单引号性能神话》 ,详细分析了此问题。 但是,几乎每个月都会有一个精力充沛的测试员来向社会揭示一种假想的性能差异。


逻辑不一致


仅从提出问题的角度来看,某些测试通常是没有意义的:例如,题为“是否真的抛出了超昂贵的操作?”的测试。 这实质上是一个问题:“处理错误真的比不处理更昂贵吗?”。 你是认真的吗 当然,在代码中添加一些基本功能会使它“变慢”。 但这并不意味着在这种荒谬的借口下根本不需要添加新功能。 如果您这样说,那么最快的程序就是什么都不做! 该程序应该是有用的,并且首先应该没有错误地工作。 而且只有在实现这一目标之后,并且只有当它运行缓慢时,才需要对其进行优化。 但是,如果问题本身没有道理,那么为什么要打扰测试性能呢? 有趣的是,即使是这种毫无意义的测试,测试人员也无法正确实施,这将在下一部分中显示。


或另一个示例,题为“ $row[id]真的比$row['id']慢吗?”的测试。 这本质上就是一个问题:“哪个代码更快-有错误或无错误的代码?” (因为在这种情况下,编写不带引号的idE_NOTICE级别的错误,并且在以后的PHP版本中将不赞成使用此类编写)。 WTF? 通常测量错误代码的性能有什么意义? 该错误应该仅仅因为它是一个错误而不是因为它会使代码运行缓慢而得到解决。 有趣的是,即使是这种毫无意义的测试,测试人员也无法正确实施,这将在下一部分中显示。


测试质量


再说一次-即使是一个无用的测试也应该是一致的,一致的-也就是说,测量可比较的值。 但是,通常,这样的测试是用左脚跟完成的,结果,获得的结果是没有意义的,与任务无关。


例如,我们的愚蠢的测试人员承诺要测量“ try..catch运算符的过度使用”。 但是在当前测试中,他不仅测量了try catch ,还测量了throw ,在循环的每次迭代中都throw了异常。 但是这样的测试是完全不正确的,因为在现实生活中,并非每次脚本执行都会发生错误。


当然,不应在PHP的Beta版本上进行测试,也不应将主流解决方案与实验解决方案进行比较。 并且如果测试人员承诺比较“ json和xml的解析速度”,则他不应在测试中使用实验功能。


一些测试只是证明测试人员对他设定的任务完全误解了。 上面已经提到了最近发表的文章中的一个类似示例:测试的作者试图找出导致错误的代码(“使用未定义的常量”)是否比没有错误的代码(使用语法正确的字符串文字)慢一些,但失败了即使使用了这种显然毫无意义的测试,还是将带引号的数字的性能与不带引号的数字的性能进行了比较。 当然,您可以在PHP中编写不带引号的数字(与字符串不同),因此,作者测试了完全不同的功能,收到了错误的结果。


还有其他要考虑的问题,例如测试环境。 PHP的扩展(例如XDebug)可能会对测试结果产生很大的影响。 或者已经提到的操作码缓存,必须在性能测试期间将其包括在内,以使测试结果至少有意义。


测试的完成方式也很重要。 由于PHP流程在每个请求之后都会完全消失,因此有必要测试整个生命周期的性能,从创建到Web服务器的连接开始到关闭此连接结束。 可以使用Apache基准测试或Siege之类的实用程序来完成此操作。


真正的性能提升


所有这些都很好,但是读者应该从本文得出什么结论? 根据定义,哪些性能测试没有用? 当然不是 但是真正重要的是他们应该开始的原因 。 从头开始测试是浪费时间。 运行性能测试应该始终有特定的原因。 这个原因被称为“分析” 。 当您的应用程序开始缓慢运行时,您需要进行性能分析,这意味着测量代码各部分的速度以找到最慢的部分。 找到这样的站点后,我们必须确定原因。 通常,这要么比所需的要大得多,要么处理的数据量大,要么对外部数据源的请求大。 对于第一种情况,优化将包括减少处理的数据量,对于第二种情况,将缓存查询结果。


例如,就性能而言,我们使用显式规定的循环还是内置的PHP函数来处理数组(本质上只是语法糖)都没有区别。 真正重要的是我们传输进行处理的数据量 。 如果它过大,我们必须修剪它,或将处理移到其他地方(到数据库)。 这将使我们获得巨大的性能提升,这将是真实的 。 虽然调用循环进行数据处理的方法之间的差异根本不太明显。


只有执行了强制性的性能改进之后,或者如果我们无法削减处理的数据量,我们才可以开始性能测试。 但是话又说回来,这样的测试不应该从头开始。 为了开始比较显式循环和内联函数的性能,我们必须确保循环是问题的原因,而不是问题的原因(破坏者:当然,这就是内容)。


我的实践中的一个最新示例:在代码中有一个使用Doctrine Query Builder的查询,该查询应该带有数千个参数。 查询本身足够快,但是Doctrine花费相当长的时间来消化数千个参数。 结果,用纯SQL重写了查询,并将参数转移到PDO库的execute()方法中,该方法几乎可以立即处理这么多参数。


这是否意味着我将永远不会使用Doctrine查询生成器? 当然不是 它非常适合99%的任务,我将继续将其用于所有查询。 而且只有在特殊情况下,才需要使用较不方便但效率更高的方法。


此选择的查询和参数是循环构造的。 如果我有一个愚蠢的想法来处理循环的调用方式,那么我只会浪费时间而没有任何积极的结果。 这就是所有性能优化的本质: 仅优化在特定情况下运行缓慢的代码 。 并不是很久以前在遥远,遥远的星系中被认为是缓慢的代码,或者不是基于无意义的测试而被人们称为缓慢的代码。

Source: https://habr.com/ru/post/zh-CN419743/


All Articles