RS Game Maker Community

Портал => Работа с GameMaker => Тема начата: CH@$ER от 16 Февраля 2008 в 09:45



Название: Азы оптимизации
Отправлено: CH@$ER от 16 Февраля 2008 в 09:45
  • Не используйте логический оператор "<>" (не равно). Вместо него можно использовать "!=".
  • Не обращайтесь к эдементам массива слишком часто (например, в каких либо вычислениях в событии шага).
  • Если есть возможность, то не используйте собственную отрисовку объектов (т.е. не нужно рисовать спрайты объектов в draw). GM работает быстрее без этого события.
  • Использовать функции вида collision_* нужно крайне осторожно. Они могут пригодится только когда нужно проверить столкновение объектов, не имеющих формы (маски). В противном случае будет достаточно instance_place.
  • Старайтесь избегать включения точной проверки столкновения у спрайтов (Precise Collision Checking), а также не используйте соответствующий флаг (prec) в функциях collision_*. В этом случае проверка столкновения будет проходить по прямоугольникам, но скорость намного выше. Часто люди просто забывают ее выключать, поэтому алгоритм работает впустую - проверяет весь спрайт, вместо того, чтобы проверить лишь пересечение прямоугольников.
  • Если вам нужно расчитать координату в заданном направлении (например, движения по кругу), то используйте lengthdir_x/y вместо синусов и косинусов. Они работают очень медленно.
  • Старайтесь избегать повторного и ненужного вычисления какой-либо переменной. Например, код:
x += lengthdir_x(point_direction(x, y, x1, y1), 10); 
y += lengthdir_y(point_direction(x, y, x1, y1), 10);
будет работать медленнее, чем:
var d;
d = point_direction(x, y, x1, y1);
x += lengthdir_x(d, 10);
y += lengthdir_y(d, 10);
  • Всегда, повторяю, ВСЕГДА заносите временные переменные в список var в начале скрипта! Иначе они останутся с объектом до конца игры, тем самым захламляя память.
  • Объеденяйте объекты с одинаковыми свойствами. Например, для стен можно сделать заготовки различных размеров - так можно сократить количество стен в комнате и существенно повысить производительность. Также можно деактивизировать эти объекты за пределами вида.
  • По возможности используйте деактивацию статических объектов (например, бочек или ящиков).
  • Игра работает медленнее при включеном режиме отладки.
  • Когда завершаете игру, не забывайте убирать все вызовы процедуры show_debug_message() - она заметно отбирает кадры в секунду.
  • Постарайтесь как можно меньше использовать вызовы собственных скриптов, особенно в частых вычислениях. Некоторые "товарищи" специально выносят код из объектов в скрипты. Это категорически противопоказано!
  • Отрисовать спрайт в виде звезды намного быстрее, чем сделать тоже самое с помощью примитивов.
  • Чем меньше текcта - тем лучше. Отрисовка текста - самая медленная из всего. Достаточно лишь представить, что каждый символ - это спрайт.
  • Удаляйте ненужные спрайты во время загрузки игры. Таковыми являются спрайты для объектов, которые в конечном итоге не будут видимыми - всякие контролееры, например.
  • Никогда не пытайтесь заменить стандартные функции своими. Они никогда не будут работать быстрее из-за того, что процедура вызова скриптов очень медленная.

Заблужденные мнения
  • Размер комнаты не влияет на размер полученного ехе-файла и не влияет на производительность. Здесь имеется ввиду изменение размера комнаты при фиксированном количестве объектов.
  • Нет никакой разници, будет ли в скрипте 100 пробелов или 1000. При загрузке игры скрипты компилируются в более оптимизированный вид. В нем нет ни комментариев, ни лишних пробелов - ничего. Поэтому рекомендуется писать код по правилам, а не в одну строчку.
  • Время загрузки игры не зависит от того, в игре ли ресурсы, или нет. Только, если, конечно, они грузятся все и сразу.
  • Верно, что собственная проверка столкновений в шаге работает быстрее, чем события столкновения, НО! Ваша проверка никогда не будет работать корректно, если в нужной области находится несколько объектов, с которыми возможно столкновение. А стандартная проверка проверяет все объекты на пересечение... собственно, отсюда и скорость ниже.


Название: Re: Азы оптимизации
Отправлено: Silen от 16 Февраля 2008 в 17:00
Хорошая статья.
Отрисовка текста - самая медленная из всего. Достаточно лишь представить, что каждый символ - это спрайт.
Пожалуй обычные виндовзские фонты скорее можно сравнить с отрисовкой примитивов. Что, впрочем, ничем не лучше )

Обнаружил один странный момент - step работает несколько медленнее, чем alarm повторяющийся каждый шаг. А учитывая, что некоторые события не к чему производить в каждом шаге, на этом можно существенно сэкономить.


Название: Re: Азы оптимизации
Отправлено: CH@$ER от 17 Февраля 2008 в 09:09
стандартные шрифты и правда, векторные, но не все, и причем:
1) они рисуются очень быстро
2) они рисуются один раз при изменении
не знаю на счет аларма, но на этот счет есть гораздо более действенный способ. когда выйду с компа расскажу


Название: Re: Азы оптимизации
Отправлено: Lex от 17 Февраля 2008 в 12:09
Вопрос про draw:
эффект достигается если события draw нет вообще, или просто надо свести к минимуму?


Название: Re: Азы оптимизации
Отправлено: Silen от 17 Февраля 2008 в 16:29
Кстати, ведь при отрисовки через draw в случае нахождения обьекта за пределами вида, он автоматически не отрисовывается и не нужно прибегать к дополнительной проверке его местоположения.


Название: Re: Азы оптимизации
Отправлено: CH@$ER от 17 Февраля 2008 в 21:51
там все гораздо сложнее... но вообще эффект достигается когда события рисования у объекта нет вообще.
то, что объект не рисуется за пределами экрана обрабатывает дикс, но ему тоже нужно помогать и не передавать лишние вершины для обработки. просто когда событие рисование присутствует, то гм тогда не сможет узнать формы выводимого объекта и следовательно не сможет узнать, находится ли этот объект в пределах экрана и передает эти лишние вершины на обработку. когда стоит стандартная отрисовка, то гм знает параметры текущего спрайта и может определить выводить ли его или нет


Название: Re: Азы оптимизации
Отправлено: CH@$ER от 18 Февраля 2008 в 12:35
Вот что я хотел сказать (это я одному чуваку объяснял):
Сначала немного определений:
Кадр. Он проходит fps раз в секунду.
Шаг. Нам необходимо, чтобы он проходил только 30 раз в секунду и не больше.

Что такое fps? Это число, показывающее количество кадров, которое рисуется в секунду. Т.е. 1000 / fps - время в миллисекундах отрисовки одного кадра. Отсюда можно заметить, что чем больше fps, тем меньше занимает времени отрисовка.
Пусть нам нужна скорость игры 30 кадров. Тогда значит один шаг должен занимать 1000 / 30 = 33.3 миллисекунд. Что если fps = 60? Тогда каждый кадр рисуется за время 16.6. Ровно в два раза быстрее, чем нужно. А что, если fps = 120? Отрисовка идет уже в четыре раза быстрее, чем нужно. Т.е. в данном случае нам нужно пропустить 4 кадра, чтобы выполнить событие шага. Но такая дискретизация нам не нужна, ведь fps может менятся с каждым кадром. Решение очень простое: заводим переменную, которая указывает текущее время, прошедшее с предыдущего шага. Каждый новый шаг оно обнуляется, а каждый новый кадр к нему добавляется время, прошедшее с предыдущего кадра (1000 / fps). Если получившееся значение больше необходимого времени между шагами (33.3), то значит выполняем событие шага и обнуляем значение времени.
Но есть одна проблема. В GameMaker FPS обновляется раз в секунду. Из-за этого получаются огромные скачки при сильной смене текущего значения fps. Вместо 1000 / fps можно использовать current_time. Тогда длинна одного кадра будет разницой времени текущего и предыдущего. Но это значение также не очень часто обновляется. Тут уж ничем не поможешь, кроме как использования дополнительной библиотеки.

Код:
У нас будет главный объект, который за всем будет следить.
Событие Create:
global.step = false; //Будет указывать для всех объектов является ли текущий кадр шагом.
prev_time = current_time;
Alarm = 0; //текущее время между шагами

Событие Begin Step:
var time;

time = current_time;
Alarm += time - prev_time; //Увеличиваем значение времени на длинну кадра
prev_time = time;

global.step = false; //Говорим, что текущий кадр не событие шага
if Alarm > 1000 / 30 //Это сделано для наглядности, лучше заменить на 33.3
{
  global.step = true;
  Alarm = Alarm - 1000 / 30; //Если счетчик перескочил немного, то делаем следующий шаг короче
}

В других объектах событие Step, Begin Step, End step начало скрипта:
if !global.step exit;

//Дальше любой код

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


Название: Re: Азы оптимизации
Отправлено: zagzi от 13 Сентября 2008 в 21:10
Постарайтесь как можно меньше использовать вызовы собственных скриптов, особенно в частых вычислениях. Некоторые "товарищи" специально выносят код из объектов в скрипты. Это категорически противопоказано!
тоесть получается что если я сделаю один скрипт к которому будут оброщатся 25 разных юнитов это будит работать хуже чем еслиб я в каждом юните написал этот скрипт?  :(
(или как всегда я не чё не понял)


Название: Re: Азы оптимизации
Отправлено: Silen от 13 Сентября 2008 в 21:22
Да. Не нужно бояться скриптов, а просто использовать их целесообразно и не выносить в них всё подряд.


Название: Re: Азы оптимизации
Отправлено: zagzi от 13 Сентября 2008 в 21:45
но разве не лутьше будет если зделать один скрипт для всех даже если он будит нечтожным?


Название: Re: Азы оптимизации
Отправлено: Lex от 13 Сентября 2008 в 22:53
лучше, но медленнее.


Название: Re: Азы оптимизации
Отправлено: Silen от 13 Сентября 2008 в 23:01
Для удобства программиста лучше, для производительности хуже. При обращение к скриптам расходуются ресурсы процессора + память на создание временных переменных для аргументов. Производительность при этом падает в 3 раза. Сам бы уже давно тест провёл.


Название: Re: Азы оптимизации
Отправлено: Lex от 13 Сентября 2008 в 23:12
Вобщем если скрипт использует много объектов, но ты его точно менять не будешь, то лучше прям в у объектов код создавай.


Название: Re: Азы оптимизации
Отправлено: Combo от 23 Сентября 2008 в 22:31
  • Старайтесь избегать включения точной проверки столкновения у спрайтов (Precise Collision Checking), а также не используйте соответствующий флаг (prec) в функциях collision_*. В этом случае проверка столкновения будет проходить по прямоугольникам, но скорость намного выше. Часто люди просто забывают ее выключать, поэтому алгоритм работает впустую - проверяет весь спрайт, вместо того, чтобы проверить лишь пересечение прямоугольников.

Что значит проверять весь спрайт, т.е. как работает алгоритм пресайз колижн?

  • Всегда, повторяю, ВСЕГДА заносите временные переменные в список var в начале скрипта! Иначе они останутся с объектом до конца игры, тем самым захламляя память.

Можно ли удалить переменную? Если да, то как?

  • По возможности используйте деактивацию статических объектов (например, бочек или ящиков).

Что происходит с деактивированным объектом? У него просто перестают проверяться все эвенты?

  • Постарайтесь как можно меньше использовать вызовы собственных скриптов, особенно в частых вычислениях. Некоторые "товарищи" специально выносят код из объектов в скрипты. Это категорически противопоказано!

Если в скрипте нету argumentX вообще, то потерь производительности не будет?
Будет ли эффективнее вместо создания скрипта создать отдельный объект с кастом эвентами, и вместо вызова скрипта вызывать event_perform_object(...)?

Еще вопрос. В дебаг моде я заметил что у объектов со спрайтом состоящим из одной картинки с каждым степом растет image_number. И, кстати, у тех, у которых спрайта нет - тоже О_о. Стоит ли у таких объектов в криэйте прописывать image_speed=0?
ЗЫ извиняюсь, если в чем то повторился.


Название: Re: Азы оптимизации
Отправлено: WertyXBOCT от 28 Сентября 2008 в 18:06
"Что значит проверять весь спрайт, т.е. как работает алгоритм пресайз колижн?"
Сравниваются все точки спрайта с точками другого спрайта, если они пересекаются то каюк)
"Можно ли удалить переменную? Если да, то как?"
Нет. Стандартными средствами ГМ нельзя
"Что происходит с деактивированным объектом? У него просто перестают проверяться все эвенты?"
Да. В целом он перестает быть активным. Все переменные которые ты сохранишь в нем будут в том же состоянии. Это уменьшает количество обращений к процессору, но увеличивает количество занимаемой памяти неактивными объектами(переменными,спрайтами,беками и т.д. простите за каламбур).


Название: Re: Азы оптимизации
Отправлено: Silen от 28 Сентября 2008 в 18:48
Стоит ли у таких объектов в криэйте прописывать image_speed=0?
Судя по всему в объекте постоянно выполняется следующий алгоритм:
if image_index<image_count then image_index+=image_speed else image_index=0
Так можно объяснить бесконечное тикание image_index. То есть, если ты выставишь image_speed=0, ничего не изменится.


Название: Re: Азы оптимизации
Отправлено: CaptainFaust от 20 Декабря 2016 в 17:58
Вставлю свои 5 копеек. Заодно может кто-то обьяснит эту магию.

Есть комната с кучей объектов и плохим ФПС. Пытаюсь улучшить ФПС. Меняю ситуацию и делаю замеры. Замеры делаются так: каждый тик счетчик FPS увеличивается на fps_real, а на экран выводится счетчик FPS разделенный на кол-во тиков. Т.е. средний FPS.

Больше всего объектов на карте, это декор (мебель, стены и т.п.) и персонажи.

И так, список действий:

1. Ничего не делаем, изначальное состояние комнаты - 70 фпс
2. Отключаем логику у дальних врагов. Поиск пути, проверка видимости игроков. Проверка типа "если расстояние до игрока больше N, то не выполнять весь следующий код" - 135 фпс
3. Пробую деактивировать дальний декор - 235 фпс
4. Пробую  деактивировать дальних врагов - 200 фпс
5. Пробую  деактивировать дальних врагов и декор - 360 фпс
6. Пробую закомментировать врагам и декору код в эвенте Draw - 185 фпс
7. Пробую прописать в эвенте Draw врагов и декора условие if(false) - 150 фпс (в ситуации когда на экране ни одного врага или элемента декора)
8. Запускаю автоматический деактиватор дальнего декора. Объект все время проходится по списку инстансов декора, проверяет расстояние до игрока, включает ближние и выключает дальние - 235 фпс
9. Запускаю деактиватор, но возвращаю просчет логики дальних врагов - 95 фпс

Собственно вопросы
1. Почему отключение декора и врагов вместе дает больший прирост чем по отдельности?
2. Почему полное комментирование (но не удаление) кода Draw дает больший прирост чем невыполнение условие обработки кода?
3. Почему  отключение логики дальних врагов само по себе дало прирост 65 фпс, но при работающем деактиваторе и возврате логики фпс упал не на 65 - с 235 до 170, а аж на 150  - с 235 до 90.


Название: Re: Азы оптимизации
Отправлено: input.txt от 20 Декабря 2016 в 20:25
2) Почему полное комментирование (но не удаление) кода Draw дает больший прирост чем невыполнение условие обработки кода?

Дело в том, что компилятор GM:S не оптимизирует условия, и в исполняемом коде так и остаётся что-то вроде:
push 0
jz 0xDEADC0DE
то есть ненужная проверка, жрущая время (впрочем немного).
Кстати, константные выражения оптимизируются на этапе компиляции: a = (2+2)*2; даст тот же код, что и a = 8;
Объявляю себя местным экспертом по байт-коду студии )


3) Почему  отключение логики дальних врагов само по себе дало прирост 65 фпс, но при работающем деактиваторе и возврате логики фпс упал не на 65 - с 235 до 170, а аж на 150  - с 235 до 90.

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


Название: Re: Азы оптимизации
Отправлено: CaptainFaust от 20 Декабря 2016 в 20:55
2) Почему полное комментирование (но не удаление) кода Draw дает больший прирост чем невыполнение условие обработки кода?

Дело в том, что компилятор GM:S не оптимизирует условия, и в исполняемом коде так и остаётся что-то вроде:
push 0
jz 0xDEADC0DE
то есть ненужная проверка, жрущая время (впрочем немного).
Кстати, константные выражения оптимизируются на этапе компиляции: a = (2+2)*2; даст тот же код, что и a = 8;
Объявляю себя местным экспертом по байт-коду студии )


3) Почему  отключение логики дальних врагов само по себе дало прирост 65 фпс, но при работающем деактиваторе и возврате логики фпс упал не на 65 - с 235 до 170, а аж на 150  - с 235 до 90.

Очевидно, деактиватор сам по себе требует вычислительных ресурсов. Попробуй замерить без логики, но с деактиватором.
2. Не обьясняет разницу между 185 и 150 fps
3. В том то и беда : БЕЗ логики и уже С деактиватором - 230 фпс. Да и врядли он требует ресурсов. Он у меня сначала перебирал 1 инстанс за 1 step. Получилось что проверка всех 650 экземпляров декора занимала почти 11 секунд. Я взял этот код в блок repeat(10) - то есть ускорил в 10 раз до 1.1 секунды - как было 230 фпс так и осталось. Увеличение до repeat(20) тоже не понизило фпс


Название: Re: Азы оптимизации
Отправлено: CaptainFaust от 20 Декабря 2016 в 21:06
если интересно то у деактиватора код onCreate такой

 :create:
ObstacleCount=instance_number(obstacle_obj);

CurrentObstacle=0;
i=0;
Obstacle[999,2]=0;
for (i=ObstacleCount-1 ; i>=0 ; i-=1)
{
    Obstacle[i,0]=instance_find(obstacle_obj,i);
    Obstacle[i,1]=Obstacle[i,0].x;
    Obstacle[i,2]=Obstacle[i,0].y;
}
//______________________________________________________________________

WallDecorCount=instance_number(wall_decor_parent_obj);
//show_message(WallDecorCount);
CurrentWallDecor=0;
i=0;
WDecor[999,2]=0;
for (i=WallDecorCount-1 ; i>=0 ; i-=1)
{
    WDecor[i,0]=instance_find(wall_decor_parent_obj,i);
    WDecor[i,1]=WDecor[i,0].x;
    WDecor[i,2]=WDecor[i,0].y;
}

MaxDistanceToPlayer=(screen_rx-screen_lx)+200;


а onStep такой

 :step:
repeat(10)
{
// Obstacles

if(instance_exists(Obstacle[CurrentObstacle,0]))
{
    Distance=point_distance(hero_obj.x,hero_obj.y,Obstacle[CurrentObstacle,1],Obstacle[CurrentObstacle,2]);
    if(Distance>MaxDistanceToPlayer)
        instance_deactivate_object(Obstacle[CurrentObstacle,0]);
}
else
{
    Distance=point_distance(hero_obj.x,hero_obj.y,Obstacle[CurrentObstacle,1],Obstacle[CurrentObstacle,2]);
    if(Distance<=MaxDistanceToPlayer)
        instance_activate_object(Obstacle[CurrentObstacle,0]);
}
CurrentObstacle+=1;
if(CurrentObstacle>=ObstacleCount)
    CurrentObstacle=0;
   
// Wal decor

if(instance_exists(WDecor[CurrentWallDecor,0]))
{
    Distance=point_distance(hero_obj.x,hero_obj.y,WDecor[CurrentWallDecor,1],WDecor[CurrentWallDecor,2]);
    if(Distance>MaxDistanceToPlayer)
        instance_deactivate_object(WDecor[CurrentWallDecor,0]);
}
else
{
    Distance=point_distance(hero_obj.x,hero_obj.y,WDecor[CurrentWallDecor,1],WDecor[CurrentWallDecor,2]);
    if(Distance<=MaxDistanceToPlayer)
        instance_activate_object(WDecor[CurrentWallDecor,0]);
}
CurrentWallDecor+=1;
if(CurrentWallDecor>=WallDecorCount)
    CurrentWallDecor=0;
   
//
}