【问题标题】:Java performance vs. code-style: Making multiple method calls from the same line of codeJava 性能与代码风格:从同一行代码进行多个方法调用
【发布时间】:2012-10-09 14:42:29
【问题描述】:

我很好奇在同一行代码中打包多个和/或嵌套的方法调用是否会更好地提高性能,这就是为什么一些开发人员这样做的原因,其代价是降低了代码的可读性。

例如

//like
Set<String> jobParamKeySet = jobParams.keySet();
Iterator<String> jobParamItrtr = jobParamKeySet.iterator();

也可以写成

//dislike    
Iterator<String> jobParamItrtr = jobParams.keySet().iterator();

就我个人而言,我讨厌后者,因为它在同一行中进行多次评估,而且我很难阅读代码。这就是为什么我尝试尽一切办法避免对每行代码进行多次评估。我也不知道 jobParams.keySet() 返回 Set 这让我很烦恼。
另一个例子是:

//dislike
Bar.processParameter(Foo.getParameter());

//like
Parameter param = Foo.getParameter();
Bar.processParameter(param);

前者让我感到恶心和头晕,因为我喜欢在每一行代码中使用简单而干净的评估,当我看到其他人的代码这样写时我只是讨厌它。

但是将多个方法调用打包在同一行中是否有任何(性能)​​优势?

编辑:单行也更难调试,感谢@stemm 的提醒

【问题讨论】:

  • 编译器可能会修复它,使其不会产生影响(在大多数情况下)
  • 单行代码也很难调试。
  • 还有一件(重要的)事情:为编译器保留微优化——做任何你(和你的团队)觉得更易读的事情。可读性是给程序员的,优化是给编译器的。
  • 我想你会发现所有这些例子最终都是相同的字节码。
  • “all on one line”示例通常被称为“train wreck”代码,例如:thomassundberg.wordpress.com/2011/12/30/…

标签: java performance coding-style


【解决方案1】:

微优化是杀手锏。如果您显示的代码引用是实例范围(或)方法范围,我将采用第二种方法。

一旦方法执行完成,方法范围变量将有资格进行 GC,因此即使您声明另一个变量,也没关系,因为范围是有限的,您获得的优势将是可读的主表代码。

【讨论】:

  • 另外,由于 jvm 中的转义分析 - 在很多情况下,局部变量可能不是在堆中创建,而是在调用堆栈中创建。从方法返回后,它们会在没有 GC 的帮助下消失。所以,我的意思是在许多情况下,局部变量根本不会导致性能开销(请参阅docs.oracle.com/javase/7/docs/technotes/guides/vm/…
【解决方案2】:

我倾向于不同意此列表中的大多数其他人。实际上,我发现第一种方式更简洁,更易于阅读。

在你的例子中:

//like
Set<String> jobParamKeySet = jobParams.keySet();
Iterator<String> jobParamItrtr = jobParamKeySet.iterator();
Could be also written as

//dislike    
Iterator<String> jobParamItrtr = jobParams.keySet().iterator();

第一种方法(你喜欢的那个)有很多不相关的信息。例如,迭代器接口的全部意义在于为您提供一个标准接口,您可以使用它来循环任何支持的实现。因此,它是一个键集这一事实与代码本身无关。您正在寻找的只是遍历已实现对象的迭代器。

其次,第二个实现实际上为您提供了更多信息。它告诉您代码将忽略 jobParams 的实现,并且只会循环遍历键。在第一个代码中,您必须首先追溯 jobParamKeySet 是什么(作为一个变量)以找出您正在迭代的内容。此外,您不知道在范围内的其他地方是否/在何处使用了 jobParamKeySet。

最后,作为最后的评论,第二种方式更容易在必要时切换实现;在第一种情况下,您可能需要重新编码两行(第一个变量赋值,如果它从一个集合更改为其他内容),而第二种情况您只需要更改一行。

话虽这么说,但凡事都有限制。在一行中链接 10 个调用可能很难阅读和调试。然而,3 或 4 个级别通常是明确的。有时,尤其是在多次需要中间变量时,显式声明它更有意义。

在你的第二个例子中:

//dislike
Bar.processParameter(Foo.getParameter());
vs

//like
Parameter param = Foo.getParameter();
Bar.processParameter(param);

我发现实际上更难理解Bar.processParameter(param) 正在处理哪些参数。我需要更长的时间才能将param 与变量实例化相匹配,才能看到它是Foo.getParameter()。而第一种情况,信息非常清晰并且呈现得非常好 - 您正在处理 Foo.getParameter() 参数。就个人而言,我发现第一种方法也不太容易出错 - 当它在同一个调用中而不是单独的行中时,您不太可能意外使用 Foo2.getParamter()

【讨论】:

  • 我完全同意。在阅读别人的代码时,声明的每个变量都是我必须在精神上跟踪的另一件事。只要行不会太长,我更喜欢链接方法调用。
  • +1 出色的写作,许多很好的论据。我什至忽略了冗余 var 也需要冗余类型声明这一点。公平地说,这可以通过使用最通用的适用接口轻松避免——但另一方面,它也可能完全搞砸,使用非常详细的实现类型。
  • “不过通常有 3 或 4 个级别是清晰的”。也许清楚,但危险。在那个 4 级链的某个地方有什么 NPE? NPE 在哪一部分?方法链通常很难测试,并且还会造成 NPE 威胁。
【解决方案3】:

变量赋值少了一个,但在某些情况下甚至编译器也可以对其进行优化。 我不会为了性能而这样做,它有点像early optimization。编写更易于维护的代码。

就我而言,我发现:

Iterator<String> jobParamItrtr = jobParams.keySet().iterator();

比以下更容易阅读:

Set<String> jobParamKeySet = jobParams.keySet();
Iterator<String> jobParamItrtr = jobParamKeySet.iterator();

但我想这是个人品味的问题。

【讨论】:

  • 即使在字节码级别优化也是微不足道的,因此我们实际上可以假设冗余变量 被优化掉。不过,并不是说我会使用这种健谈的代码风格:)
【解决方案4】:

代码永远不会由同一用户开发。我会选择第二种方式。也更容易理解和维护。

当两个不同的团队在不同的位置处理代码时,这也很有用。

很多时候,如果其他开发人员使用第一个选项,我们会花一个小时或更长时间来了解他做了什么。我个人多次遇到这种情况。

【讨论】:

    【解决方案5】:

    但是将多个方法调用打包在同一行中是否有任何(性能)​​优势?

    我严重怀疑差异是否可衡量,但即使有我也会考虑

    我很难阅读代码。

    更重要的是,它不能被夸大。

    即使速度只有一半,我仍然会编写最简单、最干净和最容易理解的代码,并且只有在您分析了应用程序并确定您有问题时,我才会考虑对其进行优化。

    顺便说一句:我更喜欢更密集的链接代码,但我建议你使用你喜欢的代码。

    【讨论】:

      【解决方案6】:

      省略一个额外的局部变量可能具有可忽略不计的性能优势(尽管 JIT 可能能够对此进行优化)。

      我个人不介意调用链接,因为它非常清楚做了什么并且中间对象不太可能为空(比如你的第一个“不喜欢”示例)。当它变得复杂时(表达式中有多个 .),我更喜欢显式的局部变量,因为它更易于调试。

      所以我根据具体情况决定我喜欢什么:)

      【讨论】:

        【解决方案7】:

        我看不出a().b().c().da.b.c.d 更难阅读,人们似乎并不太介意。 (虽然我会打破它。) 如果你不喜欢这一切都在一条线上,你可以说

        a()
         .b()
         .c()
         .d
        

        (我也不喜欢那样。) 我更喜欢使用几个额外的变量来分解它。 它使调试更容易。

        如果您关心性能(应该如此),首先要了解的是不要为小事操心。 如果添加额外的局部变量需要付出任何代价,那么在它开始变得重要之前,其余的代码都必须是无脂肪的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-09-25
          • 1970-01-01
          • 2016-12-25
          • 1970-01-01
          • 2018-09-27
          • 2019-08-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多