Объединение подклассов, которые имеют разные поля для эффективности? - PullRequest
2 голосов
/ 08 февраля 2010

Я работаю над Java-игрой для Android.Игры здесь, как правило, должны использовать пулы памяти, чтобы избежать сбора мусора.

Ради аргумента, скажем, у меня есть следующие классы:

// Enemy in the game
class Enemy { int x; int y; abstract void update(); }
// Enemy that just bounces around
class Roamer extends Enemy { int direction; ... }
// Enemy that chases a player
class Chaser extends Enemy { Player target; ... }
// maybe 20 more enemy types...

Использование подклассов является болью при использованиипулы памяти, например, вам нужно указать, например, сколько объектов Roamer и Chaser вы хотите получить заранее, а не только сколько объектов Enemy вам может понадобиться.Кроме того, вызовы виртуальных функций для обновления могут быть медленными.

Одна альтернатива, о которой я подумал, - это просто объединить все эти классы в один, например,

class Enemy
{
    int direction;
    Player target;
    int type; // i.e. ROAMER_TYPE, CHASER_TYPE
}

. Тогда я бы получил обновлениефункция, которая проверила переменную "type" и обновила соответственно.Это, очевидно, более C-подобный подход.Хотя это кажется хакерским, потому что, например, у врага-странника будет переменная «target», которую он никогда не использует.У меня вряд ли будет более 100 врагов в памяти за раз, хотя на самом деле это не имеет большого значения для памяти.Я просто хочу найти компромисс между красивым кодом и скоростью.

Есть ли у кого-нибудь комментарии о том, как лучше всего это структурировать?Занимается ли слияние таких классов слишком далеко?Это хорошая идея?Должен ли я просто использовать обычное дерево классов?

Я знаю, что нет правильных ответов на этот вопрос, но я хотел бы иметь несколько других точек зрения.

Ответы [ 4 ]

2 голосов
/ 08 февраля 2010

Композиция будет работать лучше, чем наследование здесь? Например, что-то вроде этого:

class Enemy {
    int direction;
    Player target;
    Strategy updater;
}
interface Strategy {
    void update(Enemy state);
}
class Roamer implements Strategy {
    public void update(Enemy state) {
        //Roamer logic.
    }
}
class Chaser implements Strategy {
    public void update(Enemy state) {
        //Chaser logic.
    }
}

Тогда вам потребуется только один экземпляр каждой конкретной стратегии. Например, все объекты Enemy из пула, выполняющего роль Roamer, будут использовать один экземпляр Roamer для своих обновлений. Это избавляет от необходимости хранить всю логику в одном классе, что может стать неоправданно сложным.

Если это не игра для Android, вы можете поместить карту в класс Enemy для сохранения состояния врага, который нужен одной стратегии, а другой - нет. Поскольку мы должны избегать распределений, которые могут вызвать сборщик мусора, тем не менее, может быть лучше просто иметь все члены, необходимые в классе Enemy, как вы сказали.

Вы можете использовать предварительно выделенные массивы в классе Enemy для состояния. В этом случае Chaser всегда будет хранить ссылку на Player, которого он преследует, например, в определенном индексе массива состояний. Однако это становится очень сложно.

1 голос
/ 08 февраля 2010

Кроме того, вызовы виртуальных функций для обновления могут быть медленными.

Ерунда! Стоимость выполнения вызова виртуальной функции - это пара инструкций в JIT-скомпилированном приложении. Даже в интерпретируемом режиме я бы ожидал, что он потребует еще дюжину нативных инструкций. Это из-за шума ... если вы не делаете эти звонки десятки миллионов раз в секунду.

Я бы поддержал суть ответа @ Laurence. Тратьте время на хаки производительности только после того, как вы убедились (т.е. по измерениям), что действительно есть проблем производительности, которые необходимо устранить. Если вы игнорируете это, вы рискуете сделать свой код более сложным и сложным в обслуживании, чем это должно быть. И вы, скорее всего, ухудшите производительность в своих наивных попытках сделать это лучше ... особенно с улучшением JIT-компилятора Android.

0 голосов
/ 08 февраля 2010

Вы можете скопировать пул памяти, который является внутренним для фреймворка, отсюда (см. Пул, PoolableManager, Пулы, FinitePool, SynchronizedPool):

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util

Если вы сделаете это, обязательно поместите их в свое пространство имен пакета.

Нет необходимости терять ориентацию объекта для объединения объектов. Если код по-прежнему настолько универсален, что вас пугает, используйте ту же идею для создания упрощенного универсального пула для ваших объектов.

0 голосов
/ 08 февраля 2010

Вы действительно убедились, что у вас будут проблемы с производительностью без пула памяти?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...