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

Я пишу небольшую аркадную игру на C++ (многонаправленный двухмерный космический шутер) и заканчиваю часть обнаружения столкновений.

Вот как я это организовал (я только что придумал, так что это может быть дерьмовая система):

Каждый корабль состоит из круговых компонентов — количество компонентов в каждом корабле произвольно (больше компонентов, больше процессорных циклов). У меня есть расстояние maxComponent, которое я рассчитываю при создании корабля, которое в основном представляет собой самую длинную линию, которую я могу провести от центра корабля до края самого дальнего компонента. Я слежу за вещами на экране и использую maxComponentDistance, чтобы увидеть, достаточно ли они близки, чтобы столкнуться.

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

У меня есть координаты (x,y) компонента относительно центра корабля, но они не учитывают то, как сейчас вращается корабль. Я держу их относительными, потому что не хочу пересчитывать компоненты каждый раз, когда корабль движется. Итак, у меня есть небольшая формула для расчета вращения, и я возвращаю 2d-вектор, соответствующий положению с учетом вращения относительно центра корабля.

Обнаружение столкновений находится в GameEngine и использует 2d-вектор. Мой вопрос о типах возврата. Должен ли я просто создавать и возвращать объект 2d-вектора каждый раз, когда эта функция вызывается, или я должен дать этому объекту-компоненту дополнительную частную переменную 2d-вектора, отредактировать частную переменную при вызове функции и вернуть указатель на этот объект?

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

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

Заранее спасибо.


person Chad    schedule 02.12.2008    source источник


Ответы (5)


Вы должны абсолютно избегать выделения памяти для вашего вектора-компонента при каждом вызове функции-получателя. Вместо этого делайте распределение как можно реже. Например, при изменении компонентного состава корабля, а то и реже (перераспределением).

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

Как правило (и извините, если это слишком очевидно) при выполнении такого рода обнаружения столкновений: возведите в квадрат расстояния, а не вычисляйте квадратные корни. :)

person unwind    schedule 02.12.2008
comment
Если иногда происходит несколько изменений компонентов между get, а иногда и несколько get между изменениями компонентов, то выделение при первом использовании может быть выходом. В случае однопоточного использования накладные расходы на проверку того, кэшируется ли нужное вам значение, должны быть незначительными. - person Steve Jessop; 03.12.2008

Если ваш 2D-вектор просто:

 class Vector2D { double x, y; };

Тогда обязательно верните! Например:

  Vector2D function( ... );

Или пройти по ссылке:

  void function( Vector2D * theReturnedVector2D, ... );

Избегайте любой ценой:

 vector<double> function(...);

Постоянное выделение/освобождение кучи, присущее классу Vector, является ошибкой!


Копирование вашего собственного класса Vector2D очень дешево с вычислительной точки зрения. В отличие от Vector‹›, ваш собственный класс Vector2D может включать в себя любые методы, которые вам нравятся.

Я использовал эту функцию в прошлом для включения таких методов, как DistanceToOtherPointSquared(), scanfFromCommandLineArguments(), printfNicelyFormatted() и operator[](int).


или мне следует дать этому объекту-компоненту дополнительную частную переменную 2D-вектора, отредактировать частную переменную при вызове функции и вернуть указатель на этот объект?

Следите за многократными вызовами функций, аннулирующими предыдущие данные. Это рецепт катастрофы!

person Mr.Ree    schedule 03.12.2008

  1. Вы можете начать с простого возврата вектора и его сравнения. Кто знает, может быть, это будет достаточно быстро. С помощью профилировщика вы даже можете увидеть, какая часть занимает время выполнения.
  2. Вы можете использовать пул памяти для повторного использования векторов и сокращения копирования.
  3. Вы можете попробовать шаблон легковеса для координат, чтобы уменьшить копирование и выделение, если они повторяются на протяжении всего двигатель.
  4. Сохранение данных в компоненте — это хороший способ сократить выделение памяти, но в вашем дизайне появляются некоторые подводные камни, например, тот, кто использует вектор, зависит от жизненного цикла компонента. Пул памяти, вероятно, лучше.
person orip    schedule 02.12.2008

Не используйте двумерный вектор. Вместо этого используйте vector из points. Аналогично для обнаружения столкновений. Использование 2D-вектора здесь просто неправильная структура данных.

В зависимости от содержимого функции компилятор сможет выполнять NRVO (то есть оптимизация именованного возвращаемого значения), что означает, что в оптимальном случае возврат вектора отсутствует накладных расходов, т. е. он никогда не копируется. Однако это происходит только тогда, когда вы используете возвращаемое значение вашей функции для инициализации нового экземпляра, и когда компилятор может отслеживать пути выполнения внутри функции и видеть, что для каждого возвращаемого пути возвращается один и тот же объект. Рассмотрим следующие два:

vector<int> f(int baz) {
    vector<int> ret;
    if (baz == 42)
        ret.push_back(42);
    return ret;
}

vector<int> g(int baz) {
    if (baz == 42)
        return vector<int>(1, 42);
    else
        return vector<int>();
}

Компилятор может выполнять NRVO для вызовов f, но не для g.

person Konrad Rudolph    schedule 02.12.2008
comment
Я не согласен, но я бы отметил, что возврат вектора в любом случае не очень похож на С++. Шаблонизируйте, возьмите итератор вывода в качестве параметра, такого как std::copy, и если вызывающая сторона хочет его в векторе, они могут использовать std::back_inserter, который будет встроен. По-прежнему никаких накладных расходов, и вызывающие абоненты получают больше гибкости. - person Steve Jessop; 03.12.2008
comment
Таким образом, в этом случае код шаблона‹typename IT› void f(int baz, IT it) { if (baz == 42) *it = 42; }. Вызывающий выполняет vector‹int› foo; f(42, back_inserter(foo));. Или вы можете взять итератор по неконстантной ссылке, но алгоритмы AFAIK STL этого не делают, они принимают итераторы по значению. - person Steve Jessop; 03.12.2008
comment
... конечно, все это предполагает, что вызывающие могут использовать ваши шаблоны, что в вашем собственном коде является безопасным предположением, но падает, если шаблон находится на границе библиотечного API, а ваш компилятор/компоновщик не поддерживает внешние шаблоны . Что, скажем прямо, не так. - person Steve Jessop; 03.12.2008
comment
Я ненавижу отмечать кого-либо вниз. Но вектор‹› — абсолютно неподходящий инструмент для этой работы! Роллинг его собственного класса тривиален, дешев для копирования, позволяет добавлять специальные методы Vector2D и не требует постоянной активности по созданию/удалению! (Хотя ссылка NRVO была полезной.) - person Mr.Ree; 03.12.2008
comment
onebyone: Алгоритмы STL не часто генерируют данные, и их интерфейс явно не предназначен для этой задачи. Ничто в C++ не обязывает вас придерживаться этого ограничения. Утверждение, что это не C++-ish, довольно сильно. Тем не менее, я никогда не писал код, как указано выше. - person Konrad Rudolph; 03.12.2008
comment
мри: что угодно. Я просто хотел указать, что 2D-массив (или 2D-вектор, то есть, предположительно, вложенный вектор) — совершенно неправильный инструмент для работы. vector<point> намного лучше. Конечно, написать собственную структуру данных часто даже лучше, но так же часто это ненужно и излишне. - person Konrad Rudolph; 03.12.2008
comment
mree: Забудь, что я сказал. Я только что заметил, что ОП, вероятно, относится к «вектору» в математическом смысле, а не в смысле С++. - person Konrad Rudolph; 03.12.2008

Существует большая разница между выделением памяти в куче и в стеке. Выделение в куче, например, с помощью new/delete или malloc/free происходит очень медленно. Выделение в стеке действительно происходит довольно быстро. Со стеком обычно медленная часть копирует ваш объект. Так что следите за вектором и тому подобным, но возврат простых структур, вероятно, в порядке.

person David Norman    schedule 03.12.2008