Java Temporary Iterators замедляет мою Android-игру

Этот вопрос связан с управлением памятью в Java по соображениям производительности: потому что я разрабатываю эту программу как Android-игра, а память GC убивает мою работу. Таким образом, я сделал много работы до сих пор, и выясняется, что я отлично справляюсь с оптимизацией использования памяти в своей игре, но у меня есть одна проблема: iterators!

Вот что я делаю:

  • Начало уровня игры.
  • Запустите диспетчер выделения (таким образом мы игнорируем все распределения, которые будут оставаться до тех пор, пока выполняется уровень, у меня есть много объектов, которые создаются только один раз в начале уровня, и это не проблема).
  • Сделайте несколько вещей на уровне и получите выделение.

Мои распределения полны:

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.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
}

Да, оказывается, что каждый синтаксис вызывает итератор за кулисами, чтобы заполнить каждый элемент. Что имеет смысл и именно то, что я ожидал от него, но я не понимал, что он может так сильно нараститься и вызвать проблемы с производительностью для игр. Если бы я мог избавиться от этой проблемы, это действительно заставило бы мою игру работать как молния, независимо от того, на каком телефоне она была. Поэтому мой вопрос: что бы вы сделали, чтобы все эти временные итераторы не были созданы, а затем немедленно отброшены, что привело к неприятным прогонам GC? Бонусные баллы за это делают так, что мой код не уродливый! (И использование ndk на Android не является вариантом)

P.S. Я думал, что для всех моих ArrayLists я мог бы начать использовать функцию get (int i), поскольку они представляют собой массивы за кулисами, а целое число, которое я буду использовать для индексации, которое будет помещено в стек, а не в кучу. Но для других объектов, таких как HashMap и LinkedList, я не уверен, что делать.

Ответы

Ответ 1

ArrayList и LinkedList позволяют вам перемещаться по элементам с помощью get (int i) (обратите внимание, что это может быть медленным для LinkedList, я не знаю, как реализуется get().) Это рекомендуемый подход, чтобы избежать выделения итераторов. Если вы посмотрите на исходный код платформы, вы заметите, что мы стараемся избегать использования синтаксиса for-each как можно больше.

Для HashMap вы можете захватить базовые наборы с помощью entrySet(), а затем вызвать toArray (Object []) и передать заранее выделенный массив, достаточный для хранения всех значений. Кроме того, посмотрите, можете ли вы использовать различные классы SparseArray, предлагаемые Android.

Ответ 2

Учитывая ограничения, которые вы разместили, единственными решениями являются использование индексации и либо ArrayList, либо массив. И для LinkedList и HashMap, я не думаю, что есть практическая альтернатива, которая позволит избежать распределения.

Но действительно ли GC настолько медленный, что вам действительно нужно максимально разобраться в микро-оптимизации?

Ответ 3

Цикл foreach всегда получает итератор, когда вы используете его со списками. Есть только два способа заставить его не выделять память; оба связаны с угасанием вашего кода:

  • Используйте его в необработанном массиве вместо списка (самый простой подход).

  • Используйте его в списке или другом Iterable, функция iterator() не выделяет память. Это становится хардкором и, вероятно, не стоит того, но я сделал это в тех случаях, когда я действительно хочу оптимизировать цикл. Вот как я это сделал:

    • Создайте собственный контейнерный класс, который реализует Iterable. (Цикл foreach будет работать во всем, что реализует Iterable, даже если это не подкласс List.)
    • Создайте этот класс, чтобы он не выделял память, когда он возвращает итератор через функцию iterator(). Вы можете сделать это, имея пул итераторов, захватив один и сбросив его значения. Скорее всего, вам понадобится пул вместо повторного использования одного, потому что некоторые из вашего кода могут иметь несколько вложенных циклов, повторяющихся в одной коллекции.
    • Проведите несколько часов отладки вышеупомянутого уродливого решения. В целом это не стоит в большинстве случаев.