Java for循环性能

6

什么是for循环中更好的方式?

是这个:

for(int i = 0; i<someMethod(); i++)
{//some code
 }

或者:

int a = someMethod();
for(int i = 0; i<a; i++)
{//some code
 }

假设someMethod()返回一些大的东西。

第一种方法在每个循环中执行someMethod(),从而降低了速度,第二种方法更快,但是假设应用程序中有很多类似的循环,因此声明变量a将消耗更多的内存。

那么什么更好,还是我想得太傻了。


在你的例子中,'a' 是局部变量,因此它只在定义它的块内执行时消耗内存。 - Mikhail Vladimirov
9个回答

11
第二种方式更好 - 假设someMethod()没有副作用。它实际上缓存了由someMethod()计算出的值 - 因此您无需重新计算它(假设它是一个相对昂贵的操作)。
如果有副作用,那么这两个代码片段不等效 - 您应该做正确的事情。
关于“变量a的大小” - 这不是问题,无论如何,需要在计算之前将someMethod()返回的值存储在某个中间临时变量中(即使不是这种情况,一个整数的大小也是可以忽略不计的)。
附注:在某些情况下,编译器/JIT优化器可能会将第一种代码优化为第二种代码,当然前提是没有副作用。

4
假设迭代顺序不重要,也假设你真的想微调你的代码,你可以这样做:

for (int i=someMethod(); i-->0;) {
  //some code
}

但是添加一个本地变量(你的a)并不会造成太大负担。实际上,这与你的第二个版本并没有太大区别。


2
你正在以可读性为代价进行微小优化,却没有证明更易读(显而易见)的优化版本不会同样快。 - Stephen C
嗯...我认为它并不会更难读,至少在我展示的代码中是这样。我个人认为缺点在于,当你迭代列表时,读者和维护者通常期望i会增长。 - Denys Séguret
@dstroy - 看看我的回答就能明白我在说什么了。 - Stephen C
@StephenC 我已经知道所有这些... 从我的回答和评论中不是很清楚吗? - Denys Séguret
在某些情况下,这种方法可能比增量版本更不适合缓存 - 缓存更常预取请求项之后的项目而不是之前的。 - Edward Loper
@dystroy - “从我的回答中不清楚吗?”- 没有。从你的回答中并不清楚,你推荐的解决方案(包括可读性损失)没有保证能够提高性能。这些事情需要明确地说明...因为很多SO读者英语不好,会忽略任何微妙之处...比如你使用的“可能”。 - Stephen C

4

有疑问就测试。使用分析工具。进行测量。


编写微基准测试非常困难,我总是建议不要这样做。除非您准备检查生成的汇编代码并了解JVM的优化方式,否则这是一项艰巨的任务。 - bestsss

3
如果循环后不再需要此变量,有一种简单的方法可以将其隐藏在内部:
for (int count = someMethod (), i = 0; i < count; i++)
{
    // some code
}

2

这取决于someMethod()生成输出所需的时间。此外,内存使用量也将相同,因为someMethod()首先必须生成并存储输出。第二种方法可以避免在每个循环中计算相同的输出,且不会占用更多的内存。因此第二种方法更好。


2

我认为变量a的内存消耗不是问题,因为它是一个int类型,在64位机器上需要192位。因此,我更喜欢第二种选择,因为它的执行效率更高。


1
循环优化最重要的部分是允许JVM展开循环。为了在第一种变体中实现展开,必须能够内联调用someMethod()。内联有一些预算,可能会在某个点上被耗尽。如果someMethod()足够长,JVM可能会决定不进行内联。
第二种变体更有帮助(对JIT编译器)并且可能效果更好。
我放下循环的方式是:for (int i=0, max=someMethod(); i<max; i++){...} max不会污染代码,确保没有多次调用someMethod()的副作用,并且它很紧凑(单行)。

0
如果你需要优化这个,那么这是清晰明了的做法:
int a = someMethod();
for (int i = 0; i < a; i++) {
    //some code
}

@dystroy 建议的替代版本
for (int i=someMethod(); i-->0;) {
    //some code
}

... 有三个问题。

  • 他在相反的方向上迭代。

  • 该迭代方式不符合惯用法,因此可读性较差。尤其是如果您没有遵循Java样式指南并且没有在应该放置空格的位置上放置空格。

  • 没有证明代码实际上会比更符合惯用法的版本更快...特别是一旦JIT编译器对它们进行了优化。(即使不可读的版本更快,差异也可能微不足道。)

另一方面,如果 someMethod()很昂贵(如您所述),那么将调用"提升"到仅执行一次可能是值得的。


第一个变体(在循环中调用)必须有足够的'内联预算'来内联一些方法,特别是如果它可以被覆盖。不进行内联意味着多个调用和未展开循环+可能的范围检查。 - bestsss

0

我对此有些困惑,因此使用一个包含10,000,000个整数的列表进行了一项测试。结果表明后者比前者快两秒以上:

int a = someMethod(); for(int i = 0; i<a; i++) {//some code }

我的Java 8测试结果(MacBook Pro, 2.2 GHz Intel Core i7)如下:

使用列表对象: 开始时间- 1565772380899, 结束时间- 1565772381632

在'for'表达式中调用列表: 开始时间- 1565772381633, 结束时间- 1565772384888


只是好奇,您是在回答问题还是想向对方提出另一个问题。谢谢! - surajs1n

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接