【问题标题】:Java Method invocation vs using a variableJava 方法调用与使用变量
【发布时间】:2009-12-17 18:51:28
【问题描述】:

最近,我与团队负责人讨论了使用临时变量与调用 getter 方法的问题。很长一段时间以来,我一直认为,如果我知道我将不得不多次调用一个简单的 getter 方法,我会将它放入一个临时变量中,然后改用该变量。我认为这在风格和性能方面都会更好。然而,我的领导指出,在 Java 4 和更新的版本中,这在某种程度上是不正确的。他坚信使用较小的变量空间,因此他告诉我调用 getter 方法与使用临时变量相比对性能的影响非常小,因此使用 getter 更好。然而,我并不完全相信他的论点。大家觉得呢?

【问题讨论】:

  • JVM 最喜欢内联 getter 调用。您应该首先为人类可读性编写代码,然后如果存在性能问题,请查看优化
  • 如果对性能有疑问,请测量它!这并不难,并且可以让您考虑到这些性能问题。 (如果这个方法需要 0.00000001 秒的时间来执行,这真的很重要吗?)
  • 一些支持临时变量的可靠论据:stackoverflow.com/a/19446785/1553851

标签: java performance


【解决方案1】:

永远不要为性能而编写代码,始终要为可读性而编写代码。让编译器完成工作。

他们可以改进编译器/运行时以更快地运行良好的代码,突然之间,您的“快速”代码实际上会减慢系统速度。

Java 编译器和运行时优化似乎首先解决了更常见/可读的代码,因此您的“优化”代码比刚刚编写干净的代码更有可能在以后被取消优化。

注意:

这个答案指的是Java代码“技巧”,就像引用的问题一样,不错的编程可能会将循环级别从O(N)提高到O(N ^ 2)。通常编写干净、干燥的代码并等待操作明显花费太长时间再修复它。除非您是游戏设计师,否则您几乎永远不会达到这一点。

【讨论】:

  • 我更同意这一点。 +100 可维护性和易读性远比性能重要。
  • 我同意这种精神,但不同意它的文字。表演中有很多东西。微优化只是一小部分,有时是性能关键代码的必要弊端。一个好的程序员选择他们的算法和数据结构作为规划的一部分,并且应该将性能作为设计过程的一部分。
  • @BobMcGee 我完全同意——但我区分“编程”和“优化”。当你有可用的链表时对数组列表进行插入排序是我实际上不知道如何编程的最佳示例。
  • 我同意 BobMcGee-Bill K 请更新您的答案。
  • 对不起,我还是不同意。除了糟糕的编程(例如选择错误的数据结构)之外,我认为在您真正遇到性能问题之前,您永远不应该考虑速度而不是可读性。我可以加快清理代码的速度,清理快速代码要困难得多。如果性能总是很重要,那么没有人会使用 Python(它比 Java 慢 10 倍)或 Ruby(慢 100 倍)。
【解决方案2】:

你的线索是正确的。在现代版本的 VM 中,返回私有字段的简单 getter 是内联的,这意味着不存在方法调用的性能开销。

【讨论】:

  • 呃,包括非最终的?虚拟机如何知道引用的对象不是覆盖 getter 的子类?请提供您的索赔来源。 (客观地说,函数调用的几纳秒开销几乎不重要)
  • VM 将使用分析信息来发现对属性的调用是否总是在同一个地方结束 - 请参阅en.wikipedia.org/wiki/Inline_caching
  • 内联缓存不是通过分析信息完成的 - 至少根据您链接的文章。即使有这样的技术,我认为会有一个额外的分支来创建/测试内联缓存,这对于字段访问来说是不必要的。
  • @meriton:也许我不应该这么笼统。 “简单的 getter 几乎总是由 JIT 编译器内联”。更好的?顺便说一句,这是来自 Sun 的关于该主题的研究演讲:research.sun.com/people/detlefs/talks/rice-98-talk/…。请注意日期......夏天,98。这已经有十年了。
  • 我相信,如果您查看字节码,无论哪种方式,您都会得到堆栈分配。无论是堆栈上的固定位置还是临时堆栈以传递给下一件事......或者只是增加的堆栈使用形式导致额外的 get 调用可能不会被内联。我认为在大多数情况下担心这种性能是无关紧要的,无论哪种方式。可读性是关键,有时缓存更具可读性,有时则不然。毕竟,像 getTheValueFromTheThingWithAReallyLongName() 这样的方法调用次数越少越好。 ;)
【解决方案3】:

不要忘记,通过将 getSomething() 的值分配给变量而不是调用它两次,您假设 getSomething() 在您第二次调用它时会返回相同的东西。在您所谈论的场景中,这可能是一个有效的假设,但有时并非如此。

【讨论】:

  • 推论:如果你反复调用 getSomething() ,通常你不能假设返回的值是相同的。考虑类似if ( null != getSomething()) { getSomething().doSomething(); } 的结果。
【解决方案4】:

这取决于。如果您想清楚地表明您一次又一次地使用相同的值,我会将它分配给一个临时变量。如果 getter 的调用有点长,我会这样做,比如myCustomObject.getASpecificValue()

如果代码可读,那么代码中的错误会少得多。所以这是重点。

性能差异非常小或不存在。

【讨论】:

    【解决方案5】:

    如果您牢记代码演变,v1.0 中的简单 getter 往往会在 v2.0 中变得不那么简单。

    将一个简单的 getter 更改为不那么简单的 getter 的编码人员通常不知道有一个函数会调用这个 getter 10 次而不是 1 次并且永远不会在那里纠正它,等等。

    这就是为什么从DRY principal 的角度来看,缓存值以供重复使用是有意义的。

    【讨论】:

    • +1 因为是这里唯一记得 DRY 的人。如果我确定值在调用之间保持不变,并且值必须从具有未知底层抽象的长 getter 链中获取(可能在计算上很昂贵),我认为只有疯子才会调用 getter 10x 而不是使用 temp它。此外,您通常可以为临时变量使用比 getter 更短的名称,避免 doSomethingComplexWith( ( getThisDamnedVariableOfSomeType() == null ) ? someValue : getThisDamnedVariableOfSomeType() ) 代码异味
    【解决方案6】:

    我不会牺牲“代码可读性”到几微秒。
    也许确实 getter 性能更好,并且可以在运行时为您节省几微秒。但我相信,当错误修复时间到来时,变量可以为您节省几个小时甚至几天的时间。

    对于非技术性的回答,我们深表歉意。

    【讨论】:

      【解决方案7】:

      我认为最新版本的 JVM 通常足够聪明,可以在满足某些条件时自动缓存函数调用的结果。我认为该函数必须没有副作用,并且每次调用时都必须可靠地返回相同的结果。请注意,对于简单的 getter 可能会或可能不会出现这种情况,具体取决于您的类中的其他代码对字段值执行的操作。

      如果不是这种情况并且被调用的函数进行了重要的处理,那么您确实最好将其结果缓存在一个临时变量中。虽然调用的开销可能微不足道,但如果您调用的次数超出必要,那么繁忙的方法会占用您的午餐时间。

      我也练习你的风格;即使不是出于性能原因,我发现我的代码在没有级联函数调用时更清晰。

      【讨论】:

      • 来源? (编译器如何确定方法调用将始终返回相同的结果,即同时执行的代码没有直接或间接地修改 getter 访问的状态?)
      【解决方案8】:

      如果只是getFoo(),那是不值得的。通过将其缓存到临时变量中,您不会使其更快,并且可能会自找麻烦,因为getFoo() 稍后可能会返回不同的值。但是,如果它类似于getFoo().getBar().getBaz().getSomething(),并且您知道该值不会在代码块中更改,那么可能有理由使用 temp 变量以提高可读性。

      【讨论】:

        【解决方案9】:

        一般性评论:在任何现代系统中,除了 I/O,不要担心性能问题。超快的 CPU 和大量内存意味着,所有其他问题在大多数情况下对系统的实际性能完全无关紧要。 [当然,也有像缓存解决方案这样的例外,但它们非常少见。]

        现在谈到这个特定的问题,是的,编译器将内联所有获取。然而,即使这不是真正的考虑因素,真正重要的是代码的所有可读性和流程。如果调用多次使用,比如 customer.gerOrder().getAddress(),最好用局部变量替换间接调用。

        【讨论】:

        • 我非常不同意。是的,一个好的编译器会处理小细节,但是完全忽略实现效率的问题是一个懒惰的程序员的标志。您可以通过选择 O(n log n) 工作的 O(n^2) 算法或强制执行大量不必要的字符串解析(cough XML 咳嗽)。
        【解决方案10】:

        虚拟机可以比之后声明的任何局部变量更有效地处理前四个局部变量(请参阅lload and lload_<n> instructions)。因此缓存(内联)getter 的结果实际上可能会损害您的性能。

        当然,就它们本身而言,性能影响几乎可以忽略不计,因此如果您想优化代码,请确保您真正解决了实际瓶颈!

        【讨论】:

          【解决方案11】:

          不使用临时变量来包含方法调用结果的另一个原因是使用该方法可以获得最新的值。这可能不是实际代码的问题,但更改代码时可能会出现问题。

          【讨论】:

          • 使用最新的值也可能是个问题,如果您刚刚进入基于前一个 getter 评估的代码块。现在你想对你刚刚检查过的那个值做点什么……但它已经改变了。
          【解决方案12】:

          如果您确定 getter 将在整个范围内返回相同的值,我赞成使用 temp 变量。因为如果你有一个名称长度为 10 或更多的变量,getter 在可读性方面看起来很糟糕。

          【讨论】:

          • 这是唯一的原因吗?我觉得它很弱。
          • 我认为这样做不会影响性能。如果您正在从事非常关键的项目,其中纳秒也会影响性能,那么它是您的选择。
          【解决方案13】:

          我已经用一个非常简单的代码对其进行了测试:

          • 用一个简单的 int getter 创建了一个类(我尝试了 Num 的最终值和非最终值,没有发现任何区别,请注意,在这种情况下 num 也永远不会改变......!):

            Num num = new Num(100_000_000);
            
          • 比较了 2 个不同的 for 循环:

            1: for(int i = 0; i < num.getNumber(); ++i){(...)}
            
            2: number = num.getNumber();
            for(int i = 0; i < number; ++i){(...)}
            

          结果是第一个大约 3 毫秒,第二个大约 2 毫秒。所以有一个微小的区别,对于小循环没有什么可担心的,在大迭代或者如果你总是调用 getter 并且非常需要它们时可能会出现更多问题。例如,在图像处理中,如果您想快速处理,请不要重复使用 getter,我建议...

          【讨论】:

            【解决方案14】:

            我为保存变量 +1。 1) 可读性胜过性能——你的代码不仅仅适合你。 2)性能可能可以忽略不计,但并非总是如此。我认为保持一致并开创先例很重要。因此,虽然对于一个局部变量可能无关紧要 - 它可能在一个更大的类中使用相同的值多次或在循环的情况下很重要。 3) 易于更改实现/避免 DRY 代码。现在你从这个地方用一个 getter 获得价值,理论上你在一个类中使用 getter 100 次。但在未来 - 如果您想更改获取值的位置/方式 - 现在您必须将其更改 100 次,而不是仅在将其保存为实例变量时更改一次。

            【讨论】:

              猜你喜欢
              • 2011-05-07
              • 1970-01-01
              • 2017-12-05
              • 1970-01-01
              • 1970-01-01
              • 2019-12-23
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多