Прост въпрос за ефективност C++ (разпределяне на памет)..и може би малко помощ за откриване на сблъсък?

Пиша малка аркадна игра на C++ (многопосочна 2D космическа стрелба) и завършвам частта за откриване на сблъсък.

Ето как го организирах (току-що го измислих, така че може да е скапана система):

Всеки кораб е съставен от кръгли компоненти - количеството компоненти във всеки кораб е някак произволно (повече компоненти, повече процесорни цикли). Имам maxComponent разстояние, което изчислявам при създаването на кораба, което е най-дългата линия, която мога да нарисувам от центъра на кораба до ръба на най-отдалечения компонент. Следя неща на екрана и използвам това maxComponentDistance, за да видя дали дори са достатъчно близо, за да се сблъскат.

Ако са в непосредствена близост, започвам да проверявам дали компонентите на различните кораби се пресичат. Тук идва въпросът ми за ефективността.

Имам (x,y) местоположения на компонента спрямо центъра на кораба, но не отчита как корабът се върти в момента. Поддържам ги относителни, защото не искам да преизчислявам компоненти всеки път, когато корабът се движи. Така че имам малка формула за изчисляване на въртенето и връщам 2d-вектор, съответстващ на позицията, която се съобразява с въртенето спрямо центъра на кораба.

Откриването на сблъсък е в GameEngine и използва 2d-вектора. Въпросът ми е относно видовете връщане. Трябва ли просто да създавам и връщам 2d-векторен обект при всяко извикване на тази функция, или трябва да дам на този съставен обект допълнителна частна 2d-векторна променлива, да редактирам частната променлива, когато функцията е извикана, и да връщам указател към този обект?

Не съм сигурен за ефективността на разпределението на паметта спрямо наличието на постоянна, редактируема, частна променлива. Знам, че паметта също трябва да бъде разпределена за частната променлива, но не всеки път, когато се проверява за сблъсъци, само когато се създава нов компонент. Компонентите не са постоянни в моята среда, тъй като се изтриват, когато корабът бъде унищожен.

Това е основната ми дилема. Също така бих оценил всякакви насоки относно дизайна на моята действителна система за откриване на сблъсък. За първи път го пробвам (може би трябваше да прочета малко)

Благодаря предварително.


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


Отговори (5)


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

Можете, разбира се, да проучите и пулове памет, където предварително разпределяте много такива компоненти и поставяте в пул, така че да можете да разпределяте нов компонент в постоянно време.

Като обща (и извинения, ако е твърде очевидно) точка, когато правите този вид откриване на сблъсък: повдигнете разстоянията на квадрат, вместо да изчислявате квадратни корени. :)

person unwind    schedule 02.12.2008
comment
Ако понякога има множество промени на компоненти между получавания, а понякога и множество получавания между промени на компоненти, тогава разпределянето при първо използване може да е правилният начин. В случай на използване с една нишка, разходите за проверка дали стойността, от която се нуждаете, е кеширана, трябва да са незначителни. - 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. Можете да опитате моделът Flyweight за координатите, за да намалите копирането и разпределянето, ако се повтарят през двигател.
  4. Запазването на данните в компонента е добър начин за намаляване на разпределенията, но въвежда някои проблеми във вашия дизайн, като това, че който и да използва вектора, зависи от жизнения цикъл на компонента. Пулът памет вероятно е по-добър.
person orip    schedule 02.12.2008

Не използвайте 2D вектор. По-скоро използвайте 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
Не съм съгласен, но бих отбелязал, че връщането на вектор така или иначе не е много C++-ish. Шаблонизирайте, вземете изходен итератор като параметър като std::copy и ако извикващият го иска във вектор, той може да използва std::back_inserter, който ще бъде вграден. Все още няма допълнителни разходи и обаждащите се получават повече гъвкавост. - person Steve Jessop; 03.12.2008
comment
Така че в този случай кодът е template‹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
mree: каквото и да е. Просто исках да отбележа, че 2D масив (или 2D вектор, т.е. вероятно вложен вектор) е напълно погрешен инструмент за работата. A vector<point> е много по-добър. Разбира се, писането на собствена структура от данни често е дори по-добро, но също толкова често е ненужно и прекалено. - person Konrad Rudolph; 03.12.2008
comment
mree: Забрави какво казах. Току-що забелязах, че OP вероятно се отнася до „вектор“ в математическия смисъл, а не в смисъла на C++. - person Konrad Rudolph; 03.12.2008

Има голяма разлика между разпределянето на памет в купчината и в стека. Разпределянето на купчината, например използване на new/delete или malloc/free, е много бавно. Разпределянето в стека е наистина доста бързо. При стека обикновено бавната част е копирането на вашия обект. Така че внимавайте за вектор и подобни, но връщането на прости структури вероятно е ОК.

person David Norman    schedule 03.12.2008