【问题标题】:Java use getter in for loop or create a local variable? [duplicate]Java 在 for 循环中使用 getter 或创建局部变量? [复制]
【发布时间】:2016-08-02 02:00:57
【问题描述】:

我有一个运行 4096 次的 for 循环,它应该尽可能快。性能在这里非常重要。目前我在循环中使用 getter 方法,它只返回在循环进行时不会更改的字段中的值或对象。

例子:

for (;;) {
    doSomething(example.getValue());
}

使用 getter 是否有任何开销?使用下面的方式是不是更快?

例子:

Object object = example.getValue();
for (;;) {
    doSomething(object);
}

如果是,那么访问像 example.value 这样的公共字段也是如此吗?

编辑:我不在循环内使用System.out.println()。

编辑:某些字段不是final。没有字段是volatile,也没有方法(getter)是synchronized。

【问题讨论】:

  • 将输出保存在本地字符串中会比 getter 或对象取消引用更快。但这是以“十几岁”来衡量的

标签: java performance loops for-loop getter


【解决方案1】:

作为Rogério answered,在循环外获取对象引用 (Object object = example.getValue();) 可能会比在循环内调用 getter 更快(或至少永远不会慢),因为

  • 在“最坏”的情况下,example.getValue() 实际上可能会在后台做一些计算量非常大的事情,尽管getter methods 应该是“微不足道的”。通过分配一次引用并重复使用它,您只需执行一次这种昂贵的计算。
  • 在“最佳”情况下,example.getValue() 做了一些微不足道的事情,例如 return value;,因此在 JIT 编译器 inlines the code 之后,在循环内分配它不会比在循环外更昂贵。

然而,更重要的是两者之间的语义差异及其在多线程环境中可能产生的影响:如果对象 example 的状态发生变化,导致 example.getValue() 返回对不同对象的引用,有可能在每次迭代中,方法doSomething(Object object) 将通过直接调用doSomething(example.getValue()); 来实际操作Object 的不同实例。另一方面,通过在循环外调用 getter 并设置对返回实例 (Object object = example.getValue();) 的引用,doSomething(object); 将在 object 上运行 n 次 n 迭代。

这种语义差异会导致多线程环境中的行为与单线程环境中的行为完全不同。此外,这不一定是实际的“内存中”多线程问题:如果 example.getValue() 取决于例如数据库/HDD/网络资源,这些数据可能在循环执行期间发生变化,即使Java应用程序本身是单线程的,也可能返回不同的对象。出于这个原因,最好考虑一下您真正希望通过循环实现什么,然后选择最能反映预期行为的选项。

【讨论】:

    【解决方案2】:

    很容易担心性能超出必要的程度。我知道这种感觉。需要考虑的一些事项:

    1. 4096 并不多,因此除非必须在极短的时间内完成,否则不要太担心性能。
    2. 如果在这个循环中还有其他任何昂贵的东西,getter 就无关紧要了。
    3. 过早的优化是万恶之源。首先专注于使您的代码正确和清晰。然后测量和分析它,缩小最昂贵的东西,并照顾好它。如果可能,改进实际算法。

    关于你的问题,我不知道 JIT 到底做了什么,但除非它可以确定地证明 example.getValue() 或 example.value 在循环中不会改变(这很难做到,除非该字段是final 和 getter 是微不足道的)那么从逻辑上讲,它无法避免在前一个示例中重复调用 getter,因为这可能会改变程序的行为。重复调用肯定是一些非零的额外工作。

    说了这么多,在循环外创建局部变量,不管它是否更快,因为它更清晰。也许这让你感到惊讶,但好的代码并不总是最短的。表达意图和其他信息非常重要。在这种情况下,循环外的局部变量让任何阅读代码的人都清楚doSomething 的参数不会改变(特别是如果你将其设为final),这对了解很有用。否则他们可能需要做一些额外的挖掘工作以确保他们知道程序的行为方式。

    【讨论】:

    • "那么逻辑上没有办法避免重复调用 getter" => getter 方法仍然可以内联,不管它在做什么......
    • @assylias 是的,但这仍然是一些工作,即使它只是检查字段的值。
    【解决方案3】:

    您应该更喜欢循环外的局部变量,原因如下:

    1. 它倾向于使代码更易于阅读/理解,通过在一行代码中避免像 doSomething(example.getValue()) 这样的嵌套方法调用,并允许代码为返回的值提供更好、更具体的名称getter 方法。
    2. 并非所有的 getter 方法都是微不足道的(即,它们有时会做一些可能代价高昂的工作),但开发人员通常不会注意到这一点,假设给定的方法是微不足道且成本低廉的,而实际上并非如此。在这种情况下,代码可能会在开发人员没有意识到的情况下对性能造成重大影响。提取到局部变量中往往可以避免这个问题。

    【讨论】:

      【解决方案4】:

      这取决于吸气剂。

      如果它是一个简单的 getter,JIT 无论如何都会将它内嵌到直接字段访问中,因此不会有可测量的差异。从样式的角度来看,使用 getter - 它的代码更少。

      如果 getter 正在访问 volatile 字段,则会有额外的内存访问命中,因为该值无法保存在寄存器中,但命中非常小。

      如果 getter 是synchronized,那么使用局部变量会明显更快,因为不需要每次调用都获取和释放锁,但循环代码将使用当时可能过时的字段值调用了 getter。

      【讨论】:

      • 感谢您的回答。整个代码是单线程的。所以没有可变字段或同步方法。 getter 是简单的 getter。
      【解决方案5】:

      如果你需要尽可能快地运行它,你不应该在临界区使用System.out.println。

      关于 getter:使用 getter 会有一些开销,但你不应该为此烦恼。 Java 在 JIT 编译器中确实有 getter 和 setter 优化。所以最终它们将被本机代码取代。

      【讨论】:

      • 我的代码中没有使用System.out.println()。这只是一个不好的例子。
      猜你喜欢
      • 2012-07-23
      • 2014-08-06
      • 1970-01-01
      • 2019-04-29
      • 1970-01-01
      • 2015-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多