Доступ к готовым решениям

Переход в группу "Пользователь"

300.00
Одноразовый платёж
Быстрый переход в группу "Пользователи", без надобности написания постов и ожидания.

Покупка дает возможность:
Быть полноправным участником форума
Нормальное копирование кода
Создавать темы
Скачивать файлы
Доступ к архиву Pawno-Info

Урок Тема по оптимизации

#Djuga

Эксперт
Пользователь
Регистрация
21 Сен 2017
Сообщения
1,223
Лучшие ответы
0
Репутация
232
Здравствуйте пользователи Pawno-Info

Сегодня решил сделать для вас тему по оптимизации

Всего оптимизационных трюков: 16* штук

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

[HR][/HR]

В настоящее время эта тема имеет следующие трюки:

  • Массивы медленнее обычных переменных.
    [*]
    Не используйте CallLocalFunction & funcidx, когда вы знаете имя функции заранее.
    [*]
    Нативы намного быстрее, чем PAWN код.
  • Условия в циклах.
  • Присвоение нескольким переменным одинакового значения.
  • Задержка, объявляющая локальные переменные.
  • Упрощение и переопределение математики во избежание дорогостоящих операций.
  • memcpy, strfind и так далее также работают с массивами.
  • Действительно ли стоит использовать функцию CallRemoteFunction?
  • Доступ к элементам массива несколько раз.
  • Не смешивайте дробовые и целые числа в выражениях.
  • Использование плагин стримера без необходимости.
  • Хорошее и плохое использование функций (оптимизация кода манипуляции 2D-массива)
  • Обратник
  • Switch
  • Оптимизация потребления памяти


[HR][/HR]
Некоторые из них делают значительное улучшение, а некоторые - нет. Вы можете игнорировать некоторые незначительные оптимизации и отдавать приоритет написанию читаемого кода.

[HR][/HR]

1. Массивы медленнее обычных переменных:

Следующий код неэффективен:

PHP:
new 
Float:pos[3];

GetPlayerPos(playerid, pos[0], pos[1], pos[2]);
И вот версия сборка вышеуказанного кода:

PHP:
zero.pri
addr.alt fffffff4
fill c ;These 3 instructions are responsible for zeroing all the array elements
break	; 38

addr.pri fffffff4 ;Get the address of the array
add.c 8 ;Add the index (index 2 means 2*4 bytes ahead)
load.i ;This will get the value stored at that address
push.pri ;Now push the argument

addr.pri fffffff4 ;Same as above
add.c 4
load.i
push.pri
addr.pri fffffff4 ;Same as above
load.i
push.pri
Теперь здесь приведен более эффективный код:

PHP:
new 
Float: x, 
Float: y, 
Float: z;
GetPlayerPos(playerid, x, y , z);
И вот версия сборки:

PHP:
push.c 0
push.c 0
push.c 0
push.adr fffffff4
push.adr fffffff8
push.adr fffffffc
Когда вы хотите получить доступ к элементу массива, компилятор использует следующий алгоритм:

Адрес первого элемента + 4 * Индекс = место, где хранится массив [Указатель] (эта формула верна только для одномерных массивов). После вычисления адреса элемента массива данные, хранящиеся в элементе, могут быть восстановлены. Это не означает, что вы не должны использовать массивы. Вы должны использовать массивы с умом. Не делайте массивы без причины, когда одно и то же можно просто сделать с помощью обычных переменных. По-моему, использование x, y, z на самом деле более читабельней, чем использование массива pos[3].

Тесты скорости:


PHP:
Массив: 2444, 2448, 2473
Переменные: 972 975 963
Код проверки скорости: *жмяк*

Версия без массива в 2,5 раза быстрее, чем версия с массивом.


[HR][/HR]

2. Не используйте CallLocalFunction & funcidx, когда вы знаете имя функции заранее:

Знаете ли вы, что CallLocalFunction и funcidx являются медленными функциями? Они очень медленные, потому что им нужно проверить имя функции, которое вы передаете в качестве аргумента в списке всех публичных пользователей. Это означает много внутренних манипуляций с strcmps.


PHP:
if(funcidx("OnPlayerEatBanana") == -1)
На самом деле вам не нужна эта строка. Вы можете сделать то же самое с инструкциями «0». Если вы знаете имя функции уже, просто используйте директивы pre-processor, чтобы узнать, существует ли функция.

PHP:
#if defined OnPlayerEatBanana
//OnPlayerEatBanana было объявлено
#endif
PHP:
if(CallLocalFunction("OnPlayerEatBanana","ii",play erid,bananaid"))
Вы можете сделать так:

PHP:
#if defined OnPlayerEatBanana
if(OnPlayerEatBanana(playerid,bananaid))
#else
//Эта функция не была объявлена
#endif
Тесты скорости:

PHP:
Прямой вызов: 204,226,218
CallLocalFunction: 1112,1097,1001
Обратите внимание, что это лучший случай для CallLocalFunction. В действительности CallLocalFunction будет намного медленнее, так как будет много публичных функций.

[HR][/HR]

3. Нативы намного быстрее, чем PAWN код:

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

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


PHP:
stock strcpy(dest[], src[], sz=sizeof(dest))
{
dest[0] = 0;
return strcat(dest,src,sz); //Обратите внимание, что я использовал strcat вместо написания своих собственных циклов
}
Тесты скорости:

PHP:
Стандартная: 697, 700, 718, 705
Не стандартная: 5484, 5422, 5507, 5562
И как видно, стандартная нативка в 7860 раз быстрее!!

Но не всегда.

На самом деле это предположние верно не во всех случаях. Сам по себе вызов нативной функции - сравнительно затратная операция, т.к. нужно лишний раз копировать параметры функции (из стека виртуальной машины в массив параметров для вызываемой функции - вспомните, как выглядят заголовки функций в плагинах для SA:MP), передать управление из виртуальной машины на нативный код, выполнить код функции и перейти обратно в виртуальную машину.
Собственно, выигрыш в использовании нативной функции зависит от сложности самой функции. У некоторых простых функций быстродействия может не хватить на компенсацию временных затрат на парсинг параметров и переход на нативный код и обратно.
Примеры таких функций: clamp, min, max, tolower, toupper, strfind.

Многие нативные функции предназначены для того, чего нельзя или очень сложно сделать другими средствами.
Лишь некоторые нативные функции можно заменить эквивалентным кодом на Pawn - обычно это функции для работы с целыми числами и строковые функции.


[HR][/HR]

4. Условия в циклах:

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


PHP:
for(new i = 0;i <= GetPlayerPoolSize();i++) {}
PHP:
for(new i = 0,j = GetPlayerPoolSize();i <= j;i++) {}
Видите разницу? Ладно, помогу.

В первом коде GetPlayerPoolSize вызывается для каждой итерации. GetPlayerPoolSize в наш таймфрейм, возвращает постоянное значение каждый раз, когда он вызывается. Итак, зачем просто называть GetPlayerPoolSize на каждой итерации?

Это то, что избегает второй код. Он создает локальную переменную, которая сохраняет значение, возвращаемое GetPlayerPoolSize, и использует его в состоянии. Поэтому вызов функции выполняется один раз и исключение служебных функций.


Тесты скорости:
PHP:
Оптимизация: 1102,1080,1069,1091
Без оптимизации: 2374,2359,2429,2364
Хотя улучшение приведенного выше случая может быть незначительным по сравнению с кодом, который вы помещаете внутри цикла, будут моменты, когда вы будете использовать более медленные функции.

Например:


PHP:
for(new i = 0; i < CallRemoteFunction("GetPlayersInTeam", "i", TEAM_ID); i++) {}
[HR][/HR]

5. Присвоение нескольким переменным одинакового значения.

Это моё любимое.


PHP:
x = 5;
y = 5;
z = 5;
PHP:
x = 
y = 
z = 5;
И как вы думаете, какой будет быстрее?

У первого кода есть лишние бесполезные инструкции, которые извлекают "5" снова и снова, когда он уже существует, тогда как вторая версия получает "5" только один раз и устанавливает x, y, z. Очевидный вывод состоит в том, что второй код быстрее, но это, вероятно, незначительно.

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

Тесты скорости:


PHP:
Использование memset для установки всех элементов массива (3D) из 100 элементов в ноль: 363,367,372
Установка элементов массива (3D) из 100 элементов в ноль с использованием для цикла: 6662,6642,6687
[HR][/HR]

6. Задержка, объявляющая локальные переменные

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

Плохой код:


PHP:
public OnPlayerDoSomething(playerid)
{
new actionid = GetPlayerAction(playerid), pee_id, peed_on_whome, amount_of_pee;
if(actionid == PLAYER_PEE) { }
}
Хороший код:

PHP:
public OnPlayerDoSomething(playerid)
{
new actionid = GetPlayerAction(playerid);
if(actionid == PLAYER_PEE)
{
new pee_id,peed_on_whome,amount_of_pee;
}
}
Если вы воспользовались моими предыдущими советами, вы должны знать, что когда вы создаете локальную переменную, компилятор сначала создает для нее место в стэке, а затем инициализирует ее нулем.

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

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


[HR][/HR]

7. Упрощение и переопределение математики во избежание дорогостоящих операций.

Вот классический пример, который повысит производительность этого фрагмента:

PHP:
new Float:x,Float:y,Float:z;
GetPlayerVelocity(playerid,x,y,z);
if(floatsqrt( (x*x) + (y*y) + (z*z)) > 5.0)
PHP:
new Float:x,Float:y,Float:z;
GetPlayerVelocity(playerid,x,y,z);
if( ((x*x) + (y*y) + (z*z)) > 25.0)
Вы заметили изменения?

Я скомпоновал обе стороны в условии в выражении if и исключил медленную функцию «floatsqrt».

Вот ещё пример:


PHP:
for(new i = 0, j = GetTickCount(); i < 10; i++)
{
if( j - LastTick[i] > MAX_TIME_ALLOWED)
{

}
}
PHP:
for(new i = 0, j = GetTickCount() - MAX_TIME_ALLOWED; i < 10; i++)
{
if(j > LastTick[i])
{

}
}
Я удалил MAX_TIME_ALLOWED для условия. Теперь вычитание выполняется только один раз, тогда как это делается каждый раз в первом коде. Даже это улучшение ничтожно, если только у вас нет операции, которая потре***ет много CPU.

[HR][/HR]

8. memcpy, strfind и так далее также работают с массивами.

Единственное различие заключается в том, что строка завершается нулевым символом, тогда как нормальный массив этого не делает.

PHP:
new DefaultPlayerArray[100] = {1,2,3,4,5,6,7,8,9,10};
new PlayerArray[MAX_PLAYERS][100];

for(new i = sizeof(DefaultPlayerArray); i != -1; i--)
{
PlayerArray[playerid][i] = DefaultPlayerArray[i];
}
Вот еще один эквивалентный код:

PHP:
memcpy(PlayerArray[playerid], DefaultPlayerArray, 0, sizeof(DefaultPlayerArray)*4, sizeof(PlayerArray[]));

Я сделал несколько тестов для двух кодов, и вот результаты:


PHP:
Версия цикла:
4286ms
4309ms
4410ms

Версия memcpy:
60мс
62ms
60мс

Аналогичным образом вы можете использовать strfind, strmid и многие другие строковые функции на массивах. Единственная проблема заключается в том, что когда str-функции находят в вашем массиве элемент «0», функция завершается, потому что значение 0 означает «\ 0», то есть: нулевой символ.


[HR][/HR]

9. Действительно ли стоит использовать функцию CallRemoteFunction?

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

Вы когда-нибудь думали о том, что в каждом скрипте есть анти-чит? У меня на самом деле есть один анти-хак в моем игровом режиме, который гарантирует, что измененные данные не обновляются в базе данных и еще один анти-хак в администрировании filterscript, который касается принятия мер по читу (работает независимо).

Почему два анти-читы? У нас есть два варианта: либо сделать два анти-чита, либо использовать CallRemoteFunction для обновления переменной игрока.

Иногда наличие двух отдельных анти-читов происходит быстрее, фактически некоторые из анти-чит-чеков берут четверть времени, когда CallRemoteFunction принимает вызов функции обновления.

Неважно, если вы вычисляете некоторые переменные игрока в каждом скрипте. Его способ лучше, чем обновление в одном скрипте и использование CallRemoteFunction для доступа к нему.


[HR][/HR]

10. Доступ к элементам массива несколько раз

Давайте возьмем пример, чтобы понять, о чем мы говорим здесь.

PHP:
new val = value[x][y][z];
for(new i = 50; i != -1; --i) Arr[i] = val;
PHP:
for(new i = 50; i != -1; --i) Arr[i] = value[x][y][z];
Какой, по вашему мнению, быстрее?

Первый из них быстрее, если вы внимательно прочитали Совет №2. Вы знаете, что вычисление правильного адреса из индекса массива занимает некоторое время. Во втором коде вычисление адреса выполняется каждый раз, когда значение копируется в Arr, тогда как в первом случае мы вычисляем адрес только один раз.

Тесты скорости:


PHP:
Код 1: 2280,2330,2350
Код 2: 8008 8183 8147
[HR][/HR]

11. Не смешивайте дробовые и целые числа в выражениях.

Возможно, это слишком просто, но я, по крайней мере, хотел добавить его, поскольку я вижу, что люди часто делают эту «ошибку».

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


PHP:
Код:
new Float:result = 2.0 + 1;

// Скомпилирован как
new Float:result = 2.0 + float(1);

// Что значительно медленнее, чем
new Float:result = 2.0 + 1.0;

PHP:
new mindist = 10;
if (GetPlayerDistanceFromPoint(playerid, 237.9, 115.6, 1010.2) < mindist)

// Скомпилирован как
new mindist = 10;
if (GetPlayerDistanceFromPoint(playerid, 237.9, 115.6, 1010.2) < float(mindist))

// Что значительно медленнее, чем
new Float:mindist = 10.0;
if (GetPlayerDistanceFromPoint(playerid, 237.9, 115.6, 1010.2) < mindist)
[HR][/HR]

12. Использование плагин стримера без необходимости.

Для всех стало привычкой использовать стример, даже если вам нужно всего 10 или 20 значков карты, 50 объектов и т. Д.

Вы даже знаете, что такое стример? Streamer - это плагин / include, который позволяет обойти ограничения SAMP. SAMP допускает максимум 1000 объектов, и у вас не может быть больше объектов.

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

Когда вы используете функции Streamer, например CreateDynamicObject, стример действительно не создает объект. Он добавляет информацию об объекте (X, Y, Z, RotX, RotY, RotZ ....) в базу данных объектов. После того, как определенное количество тиков / циклов сервера прошло, оно проходит через все объекты в базе данных и проверяет, есть ли игрок рядом с объектом и создает его, если это необходимо.

Вы можете видеть, что стример добавляет информацию о объекте в базу данных здесь.

Начинается обновление плеера
Вот функция, ответственная за обновление объектов.

Имеет ли смысл использовать стример, когда у вас меньше 1000 объектов?
Вам действительно нужен стример?

НЕТ!

Если вы уверены, что не собираетесь пересекать предел SAMP, вам не нужно использовать стример.

Это приносит нам новую проблему, предположим, что в вашей существующей версии у вас есть 500 объектов, но вы собираетесь обновить свой скрипт, который нуждается в 1500 объектах. Итак, теперь вам нужно преобразовать всех абонентских объектов SAMP в сторонников стримеров?

Нет, если вы написали свою первоначальную версию.


Вот что я делаю:

PHP:
#define CreateDynamicObject CreateObject
Теперь вы можете использовать CreateDynamicObject в своем коде, даже если у вас нет стримера.

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

Даже разумный способ - использовать CreateObject для объектов, которые присутствуют в популярных зонах, таких как точка появления, где вы можете предположить, что игрок будет почти всегда присутствовать в этом месте. Для объектов, находящихся в отдаленных местах, и игроки вряд ли посещают их, вы должны обязательно использовать CreateDynamicObject, потому что это те, которые не обязательно должны создаваться все время, в то время как популярные из них обязательно должны существовать все время (даже если они используются со стримерами использование стримера для таких объектов не стоит затрат).

Вам нужно будет сделать это для многих объектов, чтобы увидеть разумное улучшение, поскольку стример является плагином, и, следовательно, он довольно быстро по сравнению с PAWN-кодом.

Точно так же вы можете сделать это для других нативных функций.


[HR][/HR]

13. Хорошее и плохое использование функций (оптимизация кода манипуляции 2D-массива)

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

PHP:
native SLE_algo_foreach_list_init(list:listid, &val);
native SLE_algo_foreach_list_get(feid);

#define foreach::list(%0(%1)) for(new %1, fel_%0@%1_id = SLE_algo_foreach_list_init(%0, %1); SLE_algo_foreach_list_get(fel_%0@%1_id);)

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

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


[HR][/HR]

14. Обратник.

Часто на порталах вижу подобный код:

PHP:
new
test;
if(test == 1) test = 0;
else if(test == 0) test = 1;
/*либо такой код*/
switch(test)
{
case 0: test = 1;
case 1: test = 0;
Возникает вопрос, зачем?
Можно сделать всё проще и в одну строчку


PHP:
test = !test
И всё, не нужно никаких if, else if, switch

[HR][/HR]

15. Switch.

Часто на порталах вижу код:

PHP:
new
test;
if(test == 1) {}
else if(test == 2) {}
else if(test == 3) {}
else if(test == 4) {}
else if(test == 5) {}
else if(test == 6) {}
else if(test == 7) {}
/*И так до бесконечности*/
Возникает вопрос, зачем?
Можно сделать всё проще и быстрее по скорости выполнения кода


PHP:
switch(test)
{
case 0: {}
case 1: {}
case 2: {}
case 3:{}
}
Но желательно использовать если if, else if будет больше 3, так как тогда конструкция с if ,
else if будет быстрее


[HR][/HR]

16. Оптимизация потребления памяти.

Иногда мы видим вот такой код:

PHP:
new testick[MAX_PLAYERS];

/*И где-то в середине мода*/
testick[playerid] += 5;
Возникает вопрос, зачем?
Если переменная не имеет значения больше 255, то зачем бы нам не использовать char?


PHP:
new testick[MAX_PLAYERS char];
/*используем так*/
testick{playerid} += 5;
Не приписывайте ко всем переменным значение char. Нужно это делать с умом

[HR][/HR]
Если я допустил ошибку, то напишите про неё в комментариях. Так-же если я что-то пропустил, можете написать ваш вариант в комментариях.

[HR][/HR]
Автор с 1 по 13 пунктов - Yashas
Автор перевода и с 13 по 16 пунктов - #Djuga

[HR][/HR]

* - Вы можете добавить что-то своё и я с радостью впишу это в тему.
 
Последнее редактирование:
Сверху Снизу