Подборка правил по оптимизации проектов, проверенные на собственной "шкуре". К каждому пункту прилагается логическое обоснование, актуальность для разных версий GM и значение прироста производительности. Указанные тестовые данные могут отличаться в связи с разной сложностью кода и различными платформами (GMS). Для теста брались простые конструкции типа
repeat (1000) {a=10;}. В любом случае, каждый может проверить эффективность на себе, о чем рассказано в последнем пункте статьи.
Некоторые значения прироста производительности могут показаться слишком ничтожными, но при больших количествах таких "мелочей" это может стать достаточно заметным. Привычка формировать код сразу удобным и эффективным - хорошая привычка.
1. Лесенка из условий.
Разбивать условия, объединенные логическим оператором && (and), на несколько последовательных условий.
Пример: if a>b && b>c {} //до оптимизации
if a>b {if b>c {}} //после оптимизации
// -- обе записи идентичны в плане их результата
Почему работает: при первой записи программа проверяет
все условия в любом случае. Вторая запись уменьшает количество проверяемых условий - не всегда проверяются все условия, а,
как минимум, одно.
Актуально: для GM8.1 и ниже, и старых версий GMS. В новых версиях GMS разницы нет благодаря Short-Circuit, опция в Global Game Settings - General.
Прирост: происходит в определенных ситуациях и зависит от сложности условий.
2. (следует из предыдущего) Ступеньки лесенки условий.
При проверке составных условий на первое место ставить такие условия, которые реже всего будут истинными.
Почему работает: условие реже всего выполняется, значит, следующие за ним условия будут тоже реже выполняться. Сокращается количество проверяемых условий.
Актуально: всегда и везде.
Прирост: происходит в более частых определенных ситуациях.
3. Сокращение количества операций.
[здесь был еще неэффективный код] При проверке условий, то есть логических переменных, не сравнивать их еще раз с логическими константами. Другими словами, сокращать количество выполняемых операций.
Пример: if life=true {} //до оптимизации
if life {} //после оптимизации
// - - - - -
if life=false {} //до оптимизации
if !life {} //после оптимизации
// -- результат обоих кодов идентичен
Почему работает: используется уменьшение количества операций, производимых в действии. Например, if life=true - здесь операция сравнения переменной с true лишняя (аналогично с
if (life=true)=true), можно сократить, взяв лишь значение самой логической переменной.
Актуально: всегда и везде в разной степени.
Прирост: GM8.1-: +11%. GMS: +2%.
4. Лучше готовый результат, чем снова функция.
Если в одном коде предполагается неоднократное использование одной и той же функции (например, instance_nearest) с одними и теми же аргументами, то запишите ее результат в переменную, а при использовании обращайтесь к этой переменной.
Пример: if point_distance(x,y,instance_nearest(x,y,Enemy).x,instance_nearest(x,y,Enemy).y) < 500
{move_towards_point(instance_nearest(x,y,Enemy).x,instance_nearest(x,y,Enemy).y,5);} //до оптимизации
// - обращений к функции - 4
var target;
target=instance_nearest(x,y,Enemy);
if point_distance(x,y,target.x,target.y) < 500
{move_towards_point(target.x,target.y,5);} //после оптимизации
// - обращений к функции - 1
// -- результат обоих кодов идентичен
Кстати о переменных: если в коде необходимо использовать дополнительные переменные (как target), но которые впоследствии не потребуются (в качестве локальных переменных объекта), используйте временные переменные, как это показано в коде выше.
Если вспомогательные переменные всё-таки нужно сохранить на некоторое время вперёд или для других событий, после чего она не будет нужна - обнуляйте ее значение (особенно касается строковых переменных).
Почему работает: обращение к готовому числу намного проще, чем выполнение функции. Представьте, что point_distance - это формула
Корень(Квадрат(х2-х1)+Квадрат(у2-у1)). Очевидно, что взятие готового числа будет выполняться быстрее, чем выполнение этой функции.
Временные переменные, определяемые словом
var, существуют только в выполняемом скрипте и уничтожаются при завершении скрипта, таким образом освобождая память.
Актуально: всегда и везде.
Прирост: GM8.1-: +15%. GMS: +30%.
5. Совершенствование структуры.
Если есть возможность не использовать скрипты - не используйте скрипты. Если можете перевести кнопки в код - переводите. Если выполнение определенных действий не особо важно для того, чтобы их выполнять в шаге - выполняйте их в циклически заводящемся сигнале (раз в 2 шага, раз в секунду, но не каждый шаг).
Есть еще такие функции, которые не обязательно выполнять в шаге (или рисовании, что то же самое), а достаточно выполнить один раз. Особенно часто встречается функция draw_set_font, указанная в рисовании. Ее можно выполнить один раз в создании, как и некоторые другие функции, которые "что-то устанавливают", а не "выполняют (рисуют)" - draw_set_color, draw_set_halign, draw_set_circle_precision и другие со словом
set.
Почему работает: выполнение скрипта в шаге (например) происходит медленнее, чем просто код в шаге. По некоторым источникам, вызов скрипта в 5 раз медленнее использования обычного кода
(не подтверждено).
Кнопки работают медленнее, чем код, и к тому же могут выполнять лишние действия (для замены спрайта достаточно одного sprite_index=sprite, а кнопка выполняет еще два дополнительных действия). Между вызовом скрипта с помощью кнопки или в коде разницы нет.
Вынос действий из шага в сигнал: то, что проверки выполняются не каждый шаг, а реже, уже само говорит за себя.
Актуально: во всех версиях Game Maker в разной степени.
Прирост: Не скрипты: GM8.1-: +22%. GMS: +10%. | Не кнопки: GM8.1-: +80%. GMS: минимальный. | Сигнал: зависит от периода, всегда ярко выражен.
6. Стандартные события.
Для управления желательно использовать стандартные события клавиш, вместо проверки кода в шаге (keyboard_check). Аналогично со столкновениями с дополнительным эффектом улучшения производительности, когда столкновения не происходит (а код продолжает в это время проверять).
Почему работает: стандартные события обрабатываются быстрее, чем код в шаге.
Актуально: во всех версиях Game Maker.
Прирост: Управление: GM8.1-: +66%. GMS: +60%. | Столкновения: GM8.1-: +38%. GMS: +30%.
7. Формирование спрайтов.
Отключать точную проверку столкновений у спрайтов, если нет в ней особой необходимости.
Обрезать лишнюю пустую рамку вокруг спрайтов.
Почему работает: при точной проверке столкновений необходимо создавать маску по очертаниям спрайта. Если ее выключить, то берется прямоугольник, либо вписанные в него круг или ромб (можно сравнить с пунктом 4, только этот графический. Понятно, отчего эффект).
Лишняя рамка вокруг спрайтов увеличивает объем потребляемой памяти спрайтом и проекта в целом. В совокупности с точной проверкой, увеличивается и время создания точной маски.
Актуально: GMS.
Прирост: GM8.1-: минимальный. GMS: +19%.
8. Лучше switch, чем else if.
В ситуации, при которой различные действия должны выполняться в зависимости от значения переменной (например, проверка оружия и выполнение соответствующего выстрела), используйте switch вместо if {} else if {} else if ....
Пример: if weapon=1 {стрелять1}
else if weapon=2 {стрелять2}
else if weapon=3 {стрелять3} //до оптимизации
switch (weapon)
{case 1: стрелять1; break;
case 2: стрелять2; break;
case 3: стрелять3; break;
} //после оптимизации
// -- результат обоих кодов идентичен
Почему работает: один switch выполняется быстрее, чем несколько if.
Актуально: для всех версий Game Maker в разной степени.
Прирост: GM8.1-: +20%. GMS: +5%.
9. Объекты -> тайлы -> частицы.
Объекты, которые никаким образом не взаимодействуют с другими объектами, заменяйте их на тайлы. Если эти объекты появляются во время игры и движутся, заменяйте их на частицы, выбрав им в качестве формы собственный спрайт.
Почему работает: объект - более активная сущность, чем тайл или частица. В постановке этого пункта уже сказано чем: проверками столкновений с другими объектами, а также созданием локальных переменных, проверкой других событий.
Актуально: для всех версий Game Maker.
Прирост: Частицы vs Объекты: GM8.1-: +46%. GMS: +970%. | Тайлы vs Объекты: GM8.1-: +15%. GMS:
-3%.
10. Сборка мусора в проекте.
Если у вас в проекте появились ненужные объекты, спрайты, скрипты, комнаты или прочие ресурсы, не спешите их удалять. Создайте папку для этих объектов и перемещайте их туда. А позднее вместо того, чтобы создавать новый ресурс (объект, спрайт, скрипт, комнату), можно будет использовать старый и ненужный.
Проверяйте скачиваемые движки путём создания нового ресурса. Если появившийся порядковый номер значительно превосходит количество данного ресурса (script338563, а скриптов от силы тысяча-полторы), значит, движок создавался без соблюдения этого правила.
Почему работает: каждый ресурс требует определенного имени (более всего похожее на id объектов, index) в проекте, к которому можно будет обращаться. После удаления ресурса, этот id всё же остается в проекте и, во-первых, увеличивает размер проекта, во-вторых, замедляет процесс компиляции и увеличивает время запуска проекта, в-третьих, возможно, точно так же действует и на производительность игры - замедляет
(не подтверждено).
Избавиться от "устаревших" id ресурсов позволяет экспорт всех ресурсов в чистый проект.
Актуально: GM8.1 и ниже.
Прирост: действует только на размер файла проекта и игры, длительность компиляции и запуска. Производительность в игре не затрагивается.
11. Самостоятельные тесты.
Некоторые ситуации в коде разрешаются разными приёмами, которые могут отличаться производительностью. Если она важна для вас, проводите тестирование этих способов, выполняя код repeat (1000) {приём} в шаге или похожим способом.
- lengthdir быстрее, чем sin и cos.
- point_in_rectangle быстрее, чем if x>x1 && x<x2 быстрее, чем if abs(xc-x)<width/2 быстрее, чем if median(x1,x,x2)=x
- direction=x и speed=y немного быстрее, чем motion_set(x,y)
- (для одного объекта):
(instance_create).a=x слегка быстрее, чем inst=instance_create; inst.a=x значительно быстрее, чем with (instance_create) {a=x;} ничтожно быстрее, чем inst=instance_create; with inst {a=x;} - вычисления в Draw одинаковы по скорости с вычислениями в Step
- обращение с глобальными, локальными и временными переменными в GMS по быстродействию не различаются, однако, с использованием YYC должен быть прирост при частичном отказе от глобальных переменных. Глобальные переменные обрабатываются в GM8.0 быстрее на 5%, чем локальные и временные переменные, на что можно не обращать внимание, учитывая различные цели использования каждого вида переменных.
Почему работает: проверено на себе.
Актуально: всегда и везде.
Прирост: измеряется самостоятельно.
Автор: Fantom.
Запрещается копирование данной статьи без ведома и согласия автора.UPD 11.11.2014: проверена актуальность всех пунктов, подкреплены цифрами.