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

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

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

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

Урок Оптимизация

Статус
В этой теме нельзя размещать новые ответы.

Callback

Компетентный
Доброго времени суток, уважаемые посетители портала. Сегодня речь пойдёт об оптимизации.​


Предисловие:

PHP:
Все что вы будете делать - делаете на свой страх и риск. Я ничего Вам не навязываю.
Пункты:

- Массивы медленнее обычных переменных
- Не используйте CallLocalFunction и funcidx когда вы уверены что функция существует
- Natives быстрее кастомных функций
- Условия в циклах
- Присваивание одинаковых значений нескольким переменным
- Объявление локальных переменных
- Ускорение, за счет математических операций
- memcpy, strfind и так далее для работы с массивами
- switch быстрее чем if else
- Стоит ли использовать CallRemoteFunction
- Доступ к элементам массива несколько раз
- Не смешивайте float и integer значения

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

Давайте на секундочку представим, что у нас есть следующий код:

PHP:
new Float:pos[3];
GetPlayerPos(playerid, pos[0], pos[1], pos[2]);
GetPlayerPos(playerid, pos[0] + 5, pos[1] + 5, pos[2] + 5);

На ASM это будет выглядеть вот так:


PHP:
zero.pri
addr.alt fffffff4
fill c ; // Эти 3 инструкции отвечают за обнуление массива
break    ; 38

addr.pri fffffff4 ; // Узнаем адрес массива
add.c 8 ; // Добавляем индекс (2*4 байт)
load.i ; // Это позволит получить значение, хранящееся по этому адресу
push.pri ;

addr.pri fffffff4 ; // То же что и выше
add.c 4
load.i
push.pri
addr.pri fffffff4 ; // То же что и выше
load.i
push.pri

push.c 10 // Говорит нам сколько аргументов было получено
call myfunc
Теперь вот эквивалентный код, написанный более эффективно:

PHP:
new х, у, z;
myfunc(x, у, z);
ASM:

PHP:
push.s fffffff4 // расположение z в стэке
push.s fffffff8 // расположение y в стэке
push.s fffffffc // расположение x в стэке
push.c 10 // 12 байт ( 10 в hex - 12 )
call myfunc
Ты видишь разницу? Когда вы создаете массив, в нём инструкции для того, чтобы освободить место для массива в стэке и заполнить его нулями.
Когда вы хотите получить доступ к элементу массива, компилятор использует следующий алгоритм:
ArrayBase + Индекс = место, где хранится массив[Индекс], а затем вы получите данные, хранящиеся в этом месте.
Поэтому вы должны уже сейчас понимать, почему массивы работают медленнее, чем обычные переменные.
Это не значит, что Вы не должны использовать массивы. Вы должны вместо этого использовать массивы с умом. Не делайте массивы без причины,
когда то же самое можно просто сделать, используя обычные переменные.
На мой взгляд,используя X, Y и Z делает код более читабельным, чем с помощью массива. Так что нет ничего в поддержку массивов.

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

PHP:
Массив (10 итераций): 2444, 2448, 2473
Не Массив (10 итераций): 972, 975, 963
Обычные переменные в 2,5 раз быстрее, чем массив.

2. Не используйте CallLocalFunction и funcidx когда вы знаете что функция существует


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

Ну, например, такой код:

PHP:
if(funcidx("OnPlayerEatBanana") == -1)
На самом деле , это абсолютно ненужная проверка, ибо вы можете обойтись простой проверкой кода до компиляции, или же после компилятор выдаст Вам ошибку в виде
необъявлённой функции \ переменной или же напрямую проверить так:

PHP:
#if defined OnPlayerEatBanana
// Если функция найдена
// Так зачем лишний раз затрагивать Ваш процессор?
#endif
Это касается также CallLocalFunction.
Вместо этого:

PHP:
if(CallLocalFunction("OnPlayerEatBanana", "ii", playerid, bananaid))
Вы можете сделать так

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

PHP:
Вызов напрямую: 204, 226, 218
CallLocalFunction: 1112, 1097, 1001
Прямой вызов в 5.5 раз быстрее, чем CallLocalFunction.

Кот ещё не понял:

PHP:
myfunc(...); // быстрее
CallLocalFunction("myfunc", ...); // медленнее
Обратите внимание, что это в лучшем случае для CallLocalFunction, в реальности CallLocalFunction будет намного медленнее,
поскольку будет много функций. В коде, где проводился тест скорости, была лишь только одна public функция!

3.Стандартные natives намного быстрее, чем кастомные функции

Причина, почему native намного быстрее происходит потому, что "родные" функции непосредственно выполняются вашим компьютером, а все
кастомные функции выполняются на машине где запущен сервер.
Каждую инструкцию машина должна декодировать инструкции затем получить операнды и затем выполнить инструкцию.
Расшифровка и выборка операндов требует некоторых ресурсов процессора.

Вот пример, который использует функцию strcat чтобы скопировать две строки:

PHP:
stock strcpy(dest[], src[], sz = sizeof(dest))
{
     dest[0] = 0;
     return strcat(dest, src, sz);
}
Тесты Скорости:

PHP:
strcpy ( кастомная функция ) против родной функции strcat

strcat ( нативная стандартная функция ): 697, 700, 718, 705
strcpy ( кастомная функция ): 5484, 5422, 5507, 5562
native функция почти в 7,8 раз быстрее, чем кастомная функция!

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

Я сразу приведу Вам два примера, а вы скажите какой из них по Вашему мнению быстрее:
Пример №1:

PHP:
for(new i; i <= GetPlayerPoolSize(); i++)
Пример №2:

PHP:
for(new i, j = GetPlayerPoolSize(); i <= j; i++)
Итак, пример №2 будет работать быстрее.
Почему?
В примере №1, GetPlayerPoolSize вызывается при каждой итерации! Тогда зачем вызывать её снова и снова?

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

Также можно ещё немного улучшить выполнение цикла

PHP:
for(new i = GetPlayerPoolSize(); i != -1; i--)
// Либо
PHP:
for(new i = GetPlayerPoolSize()+1; --i != -1; )
Если Вам не особо нужен порядок выполнения цикла, то Вы можете использовать выше преведенные условия для итерации. Как видите снижается читабельность кода.

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

PHP:
С оптимизацией: 1102, 1080, 1069, 1091
Без оптимизации: 2374, 2359, 2429, 2364
Как видите выполнение стало в 2.2 раза быстрее!

Если это Вас не удивило, давайте вспомним про очень медленную CallRemoteFunction, и представим что Вы тот самый счастливчик который пишет код именно так:

PHP:
for(new i = 0; i < CallRemoteFunction("GetPlayersInTeam","i",TEAM_ID);i++)
// Посчитаем сколько веков уйдет на всё это?
5. Присвоение одинаковых значений нескольким переменным, memset


Сама функция memset: [/url]

Какой по Вашему код будет быстрее:
Вариант №1

PHP:
x = abc;
y = abc;
z = abc;
Или вариант №2

PHP:
x = 
y = 
z = abc;
// Если вы не поняли, то покажу чуть иначе:
x = y = z = abc;
Позволь я помогу тебе, показав как это выглядит на ASM
Вариант №1

PHP:
load.pri c ; // Загружаем abc
stor.pri 8 ; // Записываем в x
break    ; 20
load.pri c ; // Загружаем abc
stor.pri 4 ; // Записываем в y
break    ; 34
load.pri c ; // Загружаем abc
stor.pri 0 ; // Записываем в z
PHP:
Вариант №2

load.pri c ; // Загружаем abc
stor.pri 0 ; // Записываем в x
stor.pri 4 ; // Записываем в y
stor.pri 8 ; // Записываем в z

Позволь я помогу тебе, показав как это выглядит на ASM
Вариант №1

PHP:
load.pri c ; // Загружаем abc
stor.pri 8 ; // Записываем в x
break    ; 20
load.pri c ; // Загружаем abc
stor.pri 4 ; // Записываем в y
break    ; 34
load.pri c ; // Загружаем abc
stor.pri 0 ; // Записываем в z
Вариант №2

PHP:
load.pri c ; // Загружаем abc
stor.pri 0 ; // Записываем в x
stor.pri 4 ; // Записываем в y
stor.pri 8 ; // Записываем в z
Видите разницу? Первый вариант имеет дополнительные бесполезные инструкции, которые считывают abc снова и снова, когда его уже нет,
тогда как вторая версия получит АВС только один раз и устанавливает значения x,y и z.
Очевидный вывод заключается в том, что вариант 2 быстрее.

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

Тесты Скорости:
Устанавливаем каждому элементу 3-х мерного массива в 100 элементов, значение 0
С помощью функции memset: 363, 367, 372
Используя цикл for: 6662, 6642, 6687

memset в 18 раз быстрее, чем установка значений через цикл!

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;
      }
}
Если вы читали предыдущие советы, Вы, должно быть уже значете, что когда вы создаете локальную переменную, компилятор сначала создает некоторые места в стеке
и затем инициализирует его с нуля.
Поэтому Вы не должны не просто так создавать локальные переменные, если Вы не уверены, что собираетесь их использовать.

7.Ускорение, за счет математических операций

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

PHP:
new Float:x, Float:y, Float:z;
GetPlayerVelocity(playerid, x, y, z);
if( floatsqrt( (x*x) + (y*y) + (z*z)) > 5) // Будет медленнее, чем

PHP:
new Float:x, Float:y, Float:z;
GetPlayerVelocity(playerid, x, y, z);
if( ((x*x) + (y*y) + (z*z)) > 25) // Чем это
Вы поняли в чем были изменения?
Если вы помните курс математики 5-го класса, то должны знать что такое корень квадратный. Выше мы заменили медленную функцию floatsqrt на повышение значения в проверке.

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

PHP:
for(new i, j = GetTickCount(); ; i++)
{
     if( j - LastTick[i] > MAX_TIME_ALLOWED) // Медленнее чем
     {

     }
}

for(new i, j = GetTickCount()-MAX_TIME_ALLOWED; ; i++) // Чем это
{
     if(j  > LastTick[i] )
     {

     }
}
Как Вы поняли, вычитание происходит теперь только один раз.

8.Функции memcpy, strfind и т. д. Работа с массивами

Все строки и массивы это одно и то же. Единственное отличие заключается в том, что строка имеет нулевой символ (\0) в то время как обычный массив нет.
Мы рассмотрим этот пример, скопируем содержимое одного массива в другой:

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
PHP:
Функция memcpy:
60ms
62ms
60ms
Очевидный вывод заключается в том, что memcpy является в 73.5 раз быстрее, чем цикл!
Аналогичным образом Вы можете использовать strfind, strmid и многие другие строковые функции.
Единственная проблема заключается в том, что при нахождении str функцией элемента '0' в массиве, функция завершается, поскольку значение 0 означает '\0', то есть: нулевой символ.

9 switch быстрее чем else if

Иногда switch поможет Вам выполнить проверку в два раза быстрее:

PHP:
new playerid = 99;

if(playerid == 64){}
else if(playerid == 63) {}
else if(playerid == 62) {}
else if(playerid == 61) {}
else if(playerid == 60) {}
else if(playerid == 59) {}
else if(playerid == 58) {}
else {}

PHP:
switch(playerid)
{
        case 64: {}
        case 63: {}
        case 62: {}
        case 61: {}
        case 60: {}
        case 59: {}
        case 58: {}
        default: {}
}
Тесты скорости:

else if:

PHP:
556ms
553ms
576ms
switch:
PHP:
210ms
212ms
216ms
switch здесь почти в 2,5 раз быстрее, чем else if.

10. Стоит ли использовать CallRemoteFunction


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


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

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

PHP:
new val = value[x][y][z];
for(new i = 50; i != -1; --i) Arr[i] = val;
Вариант №2:

PHP:
for(new i = 50; i != -1; --i) Arr[i] = value[x][y][z];
Который вариант на ваш взгляд быстрее?

Первый - быстрее. Так как в первом мы не будем давать машине делать обращение к адресу массива каждую итерацию.

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

PHP:
Вариант №1: 2280, 2330, 2350
Вариант №2: 8008, 8183, 8147
Вывод очевиден, вариант 1 намного быстрее, чем вариант 2.

12.Не смешивайте Float и Int числа в выражениях


Может быть, этот слишком просто, но я вижу, как люди очень часто делают эту "ошибку".

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

Например:

PHP:
new Float:result = 2.0 + 1;
// Будет скомпилировано как:
PHP:
new Float:result = 2.0 + float(1); // Это медленно
// Это будет гораздо быстрее верхнего выражения!

PHP:
new Float:result = 2.0 + 1.0; // Это быстро
PHP:
new mindist = 10;
if (GetPlayerDistanceFromPoint(playerid, 237.9, 115.6, 1010.2) < mindist)
// Будет скомпилировано как:
PHP:
new mindist = 10;
if (GetPlayerDistanceFromPoint(playerid, 237.9, 115.6, 1010.2) < float(mindist)) // Это медленно
PHP:
new Float:mindist = 10.0;
if (GetPlayerDistanceFromPoint(playerid, 237.9, 115.6, 1010.2) < mindist) // Это быстро
Для сложных математических задач простое прибавление .0 может дать разницу в скорости в несколько процентов! На этом всё, всем спасибо и удачи!
 
Последнее редактирование:
Статус
В этой теме нельзя размещать новые ответы.
Сверху Снизу