12

由于性能原因,这个问题涉及 Java 中的内存管理:因为我正在将此程序开发为 Android 游戏,而内存 GC 会扼杀我的性能。所以到目前为止我已经做了很多工作,结果证明我在优化游戏的内存使用方面做得很好,但是我有一个问题:迭代器!

这是我正在做的事情:

  1. 开始游戏关卡。
  2. 启动分配跟踪器(这样一来,只要关卡运行,我们就会忽略所有将保留的分配;我有很多对象只在关卡开始时创建一次,它们不是问题)。
  3. 在关卡中做一些事情并获得分配。

我的分配充满了这个:

466 24 java.util.AbstractList$SimpleListIterator 12 java.util.AbstractList 迭代器
465 24 java.util.AbstractList$SimpleListIterator 12 java.util.AbstractList 迭代器
464 24 java.util.AbstractList$SimpleListIterator 12 java.util.AbstractList 迭代器
463 24 java.util.AbstractList$SimpleListIterator 12 java.util.AbstractList 迭代器
461 24 java.util.AbstractList$SimpleListIterator 12 java.util.AbstractList 迭代器
456 24 java.util.ArrayList$ArrayListIterator 12 java.util.ArrayList 迭代器
454 24 java.util.AbstractList util.ArrayList$ArrayListIterator 12 java.util.ArrayList 迭代器
453 24 java.util.ArrayList$ArrayListIterator 12 java.util.ArrayList 迭代器
452 24 java.util.ArrayList$ArrayListIterator 12 java.util.ArrayList 迭代器

所以我的游戏运行时唯一分配的对象是迭代器!好的,现在修复它......是什么代码导致了我问的问题......这里是:

for (Segment side : listOfSides.getSides()) {
    // do stuff
}

是的,事实证明 for-each 语法在后台调用迭代器来填充每个元素。这是完全合理的,也正是我期望它做的,但我没有意识到它会如此可怕地建立起来并导致游戏性能问题。如果我能摆脱这个问题,那么无论在什么手机上,它都会让我的游戏像闪电一样运行。所以我的问题是:你会怎么做才能使所有这些临时迭代器都没有被创建然后立即被丢弃,从而导致讨厌的 GC 运行?这样做的好处不会使我的代码变得完全丑陋!(并且不能在 Android 上使用 ndk)

PS 我在想,对于我所有的 ArrayList,我可以开始使用 get(int i) 函数,因为它们是幕后的数组,而我将用来索引的整数将被放置在堆栈上而不是堆上。但是对于像 HashMap 和 LinkedList 这样的其他对象,我不确定该怎么做。

4

3 回答 3

14

ArrayList 和 LinkedList 让您可以使用 get(int i) 遍历元素(注意 LinkedList 可能会很慢,我不知道 get() 是如何实现的。)这是避免分配迭代器的推荐方法。如果您查看平台的源代码,您会注意到我们尽量避免使用 for-each 语法。

对于 HashMap,您可以使用 entrySet() 获取底层集合,然后调用 toArray(Object[]) 并传递一个足够大以容纳所有值的预分配数组。或者,看看您是否可以使用 Android 提供的各种 SparseArray 类。

于 2012-07-15T07:41:31.527 回答
1

鉴于您放置的约束,唯一的解决方案是使用索引和一个ArrayList或一个数组。对于LinkedListand HashMap,我认为没有一种实用的替代方案可以避免任何分配。

但是GC真的那么慢,真的需要像这样去微优化到极致吗?

于 2012-07-15T07:40:36.447 回答
0

当您将 foreach 循环与 Lists 一起使用时,它总是会获取迭代器。只有两种方法可以让它不分配内存;两者都涉及丑化你的代码:

  • 在原始数组而不是 List 上使用它(最简单的方法)。

  • 在其 iterator() 函数不分配内存的 List 或其他 Iterable 上使用它。这有点硬核,可能不值得,但在我真的想优化循环的情况下我已经这样做了。我是这样做的:

    1. 创建自己的实现 Iterable 的容器类。(foreach 循环适用于任何实现 Iterable 的东西,即使它不是 List 的子类。)
    2. 使该类在通过 iterator() 函数返回迭代器时不分配内存。您可以通过拥有一个迭代器池、抓取一个并重置其值来做到这一点。您可能需要一个池而不是仅仅重用一个池,因为您的某些代码可能有多个嵌套循环迭代同一个集合。
    3. 花几个小时调试上述丑陋的解决方案。总而言之,在大多数情况下它是不值得的。
于 2012-07-16T03:35:40.573 回答