【问题标题】:Efficiency of java for loopsjava for循环的效率
【发布时间】:2012-09-12 15:31:43
【问题描述】:

作为这个问题的附录Java loop efficiency ("for" vs. "foreach")

我有一个简单的问题。增强的 for 循环是否具有更大的内存占用,或者它们会编译成相同的结构,使它们的内存占用相同,从而使for(Object o : collection) { ... } 总是更适合只读操作?

我问的原因是,在我正在使用的模拟器中,每一帧(60 帧/秒)我都在对数组中可能有数千个项目运行操作,例如要绘制的面、要针对光线跟踪等。循环

for(Object o : collection) {
   o.doSomething(); 
}

看起来它可以制作集合的内存副本(我似乎记得在我之前的阅读中确实如此)这在大多数情况下是可以的,但如果你每秒执行 30000 次光线追踪乘以 60 次则不行。

另一方面,循环很明显

count = collection.size();
for(i = 0; i < count; i++) {
   collection[i].doSomething();
}

所有内容都通过参考进行,并且占用空间相对较小,尽管它更难阅读(虽然老实说,并不多)

各位,有什么想法吗?

(注意:阅读 for 循环的难度的影响仅在您进入多层时才明显 - 对于单层 fors 增益非常小。我从经验中这么说...collection[i].property.subcollection[j].row[k].col[l].prop.subtable[m] 成为一个令人心碎的人,特别是如果其中一些需要演员:例如((Type3)((Type2)((Type1)collection[i]).property.subcollection[j].row[k]).col[l].prop).subtable[m]。)

【问题讨论】:

  • 好吧,我至少可以告诉您,第二个实际上允许您更改列表而不会破坏迭代器,也就是 ConcurrentModificationException 地狱。这确实很有用。
  • 你的第二个循环不会编译。 collection 是数组还是 Collection
  • 增强 for 循环将变成基于迭代器的循环。因此,它的开销可能会稍高一些。但是,JIT 可能会摆脱这种开销。
  • @RiverC - 除非你只使用Iterator.remove()...
  • @RiverC:我只是说数组的 foreach 循环编译为与Iterable 不同的字节码。我想这与您的问题有关...

标签: java memory-management loops for-loop


【解决方案1】:

如果您有一个使用 hte 的 ArrayList,则可以进行微优化

for(int i=0, len=list.size(); i < len; i++)

您节省的内存约为 16-24 字节,因为 for-each 循环将始终创建一个迭代器。

(60/秒)我正在对数组中可能有数千个项目运行操作,例如要绘制的面、要针对光线跟踪的对象,

对于每秒 60,000 次,您不太可能注意到差异,尤其是因为您对每个项目都进行了相对重要的工作。

看起来它可能会制作集合的内存副本

这就是为什么如果在循环遍历列表时更改列表会得到 ConcurrentModicationException 的原因。一种解决方法是复制一份。

【讨论】:

  • 60000+ 操作不在同一个列表(60000 的简单列表)上,而是在某些情况下是多维的多个列表上。这只是为了澄清。
  • 好的,所以如果您每秒迭代超过 60000 个列表,那么使用随机访问可能比使用 for-each 更好,因为它每次都保存一个迭代器。
  • 迭代器是否每次都被销毁,或者集合(如果不是数组)是否重用其迭代器?
  • 此外,在某些情况下,我使用数组,因为它们的内存开销较小 - 即地图中的数据点可能会将集合数据附加到每个点,因此我在那里使用数组以避免有数千个集合。
  • @RiverC 每次您请求迭代器时,您都会得到一个新的迭代器。这是必不可少的,因为它保留了您自己的个人当前迭代的状态,即您目前的状态。
【解决方案2】:

很难说 JIT 将如何优化您的代码(Java 的哪个实现、哪个操作系统等)。这里的要点是,for-each 语法编译为与使用迭代器语法相同的字节码,参见herehere。使用迭代器语法的主要原因是在迭代期间删除项目。注意Iterator接口的remove方法是可选的。

我自己做了一些简单的例子来查看字节码:

对于数组的每次迭代

// compiled .class file is 585 bytes
public class ForEachLoop {
  public static void main(String[] args) {
    for(String s : args){
      System.out.println(s);
    }
  }
}

// byte code for main method
public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=5, Args_size=1
   0:   aload_0
   1:   dup
   2:   astore  4
   4:   arraylength
   5:   istore_3
   6:   iconst_0
   7:   istore_2
   8:   goto    26
   11:  aload   4
   13:  iload_2
   14:  aaload
   15:  astore_1
   16:  getstatic   #16; //Field java/lang/System.out:Ljava/io/PrintStream;
   19:  aload_1
   20:  invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   23:  iinc    2, 1
   26:  iload_2
   27:  iload_3
   28:  if_icmplt   11
   31:  return
  LineNumberTable: 
   line 4: 0
   line 5: 16
   line 4: 23
   line 7: 31

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      32      0    args       [Ljava/lang/String;
   16      7      1    s       Ljava/lang/String;

数组索引循环

// compiled .class file is 554 bytes
public class ArrayLoop {
  public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
      System.out.println(args[i]);
    }
  }
}

// byte code for main method
public static void main(java.lang.String[]);
  Code:
   Stack=3, Locals=2, Args_size=1
   0:   iconst_0
   1:   istore_1
   2:   goto    17
   5:   getstatic   #16; //Field java/lang/System.out:Ljava/io/PrintStream;
   8:   aload_0
   9:   iload_1
   10:  aaload
   11:  invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  iinc    1, 1
   17:  iload_1
   18:  aload_0
   19:  arraylength
   20:  if_icmplt   5
   23:  return
  LineNumberTable: 
   line 4: 0
   line 5: 5
   line 4: 14
   line 7: 23

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      24      0    args       [Ljava/lang/String;
   2      21      1    i       I

我们可以看到数组迭代字节码更紧凑(准确地说是31字节)。

【讨论】:

  • 不是每个循环数组的特殊情况吗? afaik 没有涉及迭代器,是吗?
  • 数组的情况如何(我有时会使用它,因为它们的内存占用很小)?
  • 关于您的更新。很酷知道!因此,您在正常的 for 循环中占用的程序内存更少。
猜你喜欢
  • 2012-03-02
  • 1970-01-01
  • 1970-01-01
  • 2013-04-18
  • 2014-09-02
  • 2019-11-06
  • 2020-10-10
  • 2015-02-15
  • 1970-01-01
相关资源
最近更新 更多