What is the Cost of Calling array.length
在我们的应用程序中将for循环更新为for-each循环时,我遇到了很多这样的"模式":
1 2 3 | for (int i = 0, n = a.length; i < n; i++) { ... } |
代替
1 2 3 | for (int i = 0; i < a.length; i++) { ... } |
我可以看到您获得了集合的性能,因为您不需要在每个循环中调用size()方法。 但是有了数组?
因此出现了一个问题:
不,对
由于
无论如何,现代的JIT编译器很可能会立即优化对
您可以通过查看OpenJDK中的JIT编译器的源代码,或者通过使JVM转储JIT编译的本机代码并检查代码来确认这一点。
注意,在某些情况下,JIT编译器无法做到这一点;例如
我在午餐时间有一点时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void main(String[] args) { final int[] a = new int[250000000]; long t; for (int j = 0; j < 10; j++) { t = System.currentTimeMillis(); for (int i = 0, n = a.length; i < n; i++) { int x = a[i]; } System.out.println("n = a.length:" + (System.currentTimeMillis() - t)); t = System.currentTimeMillis(); for (int i = 0; i < a.length; i++) { int x = a[i]; } System.out.println("i < a.length:" + (System.currentTimeMillis() - t)); } } |
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | n = a.length: 672 i < a.length: 516 n = a.length: 640 i < a.length: 516 n = a.length: 656 i < a.length: 516 n = a.length: 656 i < a.length: 516 n = a.length: 640 i < a.length: 532 n = a.length: 640 i < a.length: 531 n = a.length: 641 i < a.length: 516 n = a.length: 656 i < a.length: 531 n = a.length: 656 i < a.length: 516 n = a.length: 656 i < a.length: 516 |
笔记:
关键是,这是其他所有人都在做的事情,您必须在内存不足的情况下运行Java,并且您仍然看不到这两种选择之间的速度差异显着。将开发时间花在实际重要的事情上。
我怀疑是否存在任何重大差异,即使存在差异,我敢打赌它可能在编译期间已被优化。当您尝试对诸如此类的事情进行微优化时,您正在浪费时间。首先使代码可读性和正确性,然后如果遇到性能问题,请使用探查器,然后担心在适当时选择更好的数据结构/算法,然后担心优化探查器突出显示的部分。
数组的长度在Java中作为数组的成员变量(与元素不同)存储,因此获取该长度是恒定时间的操作,与从常规类中读取成员变量相同。许多较旧的语言(例如C和C ++)不会将长度存储为数组的一部分,因此您需要在循环开始之前存储长度。您无需在Java中执行此操作。
在这种情况下,为什么不进行反向循环:
1 2 3 | for (int i = a.length - 1; i >= 0; --i) { ... } |
这里有2个微优化:
- 循环反转
- 后缀递减
将其存储在变量中可能稍微快一些。但是,如果分析器指出这是一个问题,我将感到非常惊讶。
在字节码级别,使用
这个答案是从C#的角度来看的,但我认为Java同样适用。
在C#中,这个成语
1 | for (int i = 0; i < a.length; i++) { ...} |
被认为是遍历数组,因此在循环中访问数组时(而不是每次访问数组时)都避免了边界检查。
这可能会或可能不会被以下代码识别:
1 | for (int i = 0, n = a.length; i < n; i++) { ...} |
要么
1 2 3 | n = a.length; for (int i = 0; i < n; i++) { ...} |
这种优化有多少是由编译器和我不知道的JITter执行的,特别是如果它是由JITter执行的,我希望所有这3种都能生成相同的本机代码。
但是,第一种形式也可以说是人们更易读的,所以我想说一遍。
Array.length是一个常量,JIT编译器应该在两种情况下都可以看到该常量。我希望两种情况下产生的机器代码都相同。至少对于服务器编译器。