【问题标题】:How to write Java for loops to avoid repeatedly computing the upper bound如何编写Java for循环以避免重复计算上限
【发布时间】:2014-10-21 19:14:12
【问题描述】:

我一般会写

for (int i = 0, n = someMethod(); i < n; i++)

优先于

for (int i = 0; i < someMethod(); i++)

避免someMethod() 被重复计算。但是,我永远不确定何时需要这样做。 Java 在识别每次都会给出相同结果并且只需要在循环开始时执行一次的方法方面有多聪明?

【问题讨论】:

  • 为什么你不能像 `int n = someMethod();'然后在循环中使用 n?
  • Adi,这基本上就是我在第一个示例中所做的。我的问题是什么时候需要先计算 n 。我确信在某些情况下,我编写的第二种方式无论如何都会像第一种方式一样被对待,但我可能错了。
  • 即使每次都给出相同的结果,Java 是否可以确定该方法不会执行其他操作(例如日志记录)。我认为它必须每次都调用。
  • 您的第一个样式比在循环外声明上限的某些答案中的示例要好。尽可能将变量保持在最紧凑的范围内使代码更易于理解。

标签: java optimization jit


【解决方案1】:

我认为,作为程序员,您有责任确定需要即时计算 for-loop 上限的情况并相应地解决它们。

假设n 的值不依赖于循环内执行的某些操作,我个人更喜欢将其写成

int n = someMethod();
for (int i = 0; i < n; i++);

因为它保留了for-loop 最常见的样式,同时明确定义了上限。

【讨论】:

  • 我同意责任在我身上。我只是出于学术兴趣而问。我总是先计算 n。我仍然更喜欢像在第一个示例中那样编写它,以将 n 的范围限制为循环。
  • @Paul 完全可以理解。关于范围:这对我来说是情境性的,但增强的可读性通常会胜出。
【解决方案2】:

据我所知,JIT 仅在它是一种相当简单的可内联方法时才会检测到这一点。话虽如此,程序员很容易检测到这些情况,而 JIT 编译器很难检测到。最好使用final int 来缓存大型方法的结果,因为 JIT 可以很容易地检测到该值不能更改,甚至可以删除数组访问检查以加快循环。

类似

int[] arr = new int[ 10 ];
for( int i = 0; i < arr.length; i++ ) {
    //...
}

List< String > list = Arrays.asList( new String[] { ... } );
for( int i = 0; i < list.size(); i++ ) {
    //...
}

可能很容易被 JIT 优化。其他调用大型或复杂方法的循环不容易被证明总是返回相同的值,但像 size() 这样的方法可能会被内联甚至完全删除。

最后在数组上使用 for-each 循环。在数组的情况下,它们会衰减到我发布的第一个循环,并且也可以很容易地进行优化以产生最快的循环。尽管非数组上的 for-each 循环,但在涉及快速循环时我更愿意避免,因为它们会衰减为迭代器循环,而不是我发布的第二个循环。这对于 LinkedList 来说不是这样,因为迭代器比使用 get() 更快,因为 O(n) 遍历。

这都是关于 JIT 可以做什么来优化循环的猜测。重要的是要知道 JIT 只会优化它可以证明不会改变结果的东西。保持简单将使 JIT 的工作更容易。就像使用 final 关键字一样。在值或方法上使用 final 可以让 JIT 轻松证明它不会改变并且可以像疯了一样内联。这就是 JIT 最重要的优化,内联。让这项工作变得简单,JIT 将极大地帮助您。

这是一个link 讨论循环优化,如果 JIT 不能证明它的优化不会改变任何东西,它就不能总是优化循环。

【讨论】:

  • 谢谢你,这是一个非常彻底的答案。我真的很想知道除了 size() 之外是否还有其他常用方法以这种方式内联。在您的第二个示例中,如果第一行是 List list = Arrays.asList(arr);相反,从技术上讲 size() 可能会在循环中发生变化。在这种情况下,JIT 是否能够识别何时没有在循环和内联 size() 中修改 arr?
  • @PaulBoddington 从技术上讲,arr.length 也可以随时更改。由于 ArrayList 上的 List.get() 和 List.size() 基本上只是围绕单个数组访问的包装器。我假设一旦被内联,它将看起来很像第一个循环。这可能足以让 JIT 继续优化它。
  • @PaulBoddington 我想我在这里想说的更多是 JIT 在内联方面要好得多。这是它的主要优化。它会动起来,而且非常有效。当涉及到循环时,检测不变的值最有利于消除越界检查。这可能是 JIT 可以执行的第二个最有益的优化。
  • 我刚刚记得数组的长度是固定的,所以之前的cmets是不正确的。
  • @PaulBoddington 但参考可能会改变。我的意思是这很容易检测到,但它与删除所有抽象后添加到 ArrayList 是一样的。
猜你喜欢
  • 1970-01-01
  • 2019-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-10
  • 2019-06-24
  • 1970-01-01
相关资源
最近更新 更多