Java 在 for 循环中使用 getter 还是创建局部变量?

Java use getter in for loop or create a local variable?

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

示例:

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

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

示例:

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

如果是,那么访问 public 字段(如 example.value 也是如此吗?

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

编辑:有些字段不是 final。没有字段是 volatile 并且没有方法 (getter) 是 synchronized.

如果你需要尽快运行,你不应该在关键部分使用System.out.println

关于 getter:使用 getter 会产生轻微的开销,但您不必为此烦恼。 Java 在 JIT 编译器中确实有 getter 和 setter 优化。所以最终它们将被本机代码取代。

这取决于getter。

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

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

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

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

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

很容易过度担心性能。我明白这感受。需要考虑的一些事项:

  1. 4096 并不多,所以除非必须在极短的时间内完成,否则不要太担心性能。
  2. 如果此循环中还有其他任何昂贵的事情发生,getter 将无关紧要。
  3. 过早的优化是万恶之源。首先专注于使您的代码正确和清晰。然后对其进行测量和分析,缩小最昂贵的范围,并处理好它。如果可能的话,改进实际的算法。

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

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

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

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

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

语义上的这种差异会导致多线程环境中的行为与单线程环境中的行为截然不同。此外,这不一定是一个实际的 "in-memory" 多线程问题:如果 example.getValue() 取决于例如database/HDD/network 资源,此数据可能会在循环执行期间发生变化,因此即使 Java 应用程序本身是单线程的,也可能会 returned 不同的对象。出于这个原因,最好考虑您实际想要通过循环完成什么,然后选择最能反映预期行为的选项。