Форум Pawn-Wiki.Ru - Воплоти мечту в реальность!: Новые способы оптимизации кода - Форум Pawn-Wiki.Ru - Воплоти мечту в реальность!

Перейти к содержимому

Страница 1 из 1
  • Вы не можете создать новую тему
  • Вы не можете ответить в тему

[ Урок ]
Новые способы оптимизации кода
Оценка: ***** 2 Голосов

#1
Пользователь офлайн   AloneAmigo 

  • Знаток
  • Вставить ник
  • Раскрыть информацию
От переводчика: Довольно давно обнаружил я эту занятную тему и долгое время удивлялся, почему нет русского эквивалента. Надеюсь, сумел сделать достойный перевод. Итак,

Новые способы оптимизации кода
Я поделюсь с вами своими способами, с помощью которых можно оптимизировать код. Здесь будут те, которые вы уже видели раннее, но в них есть пара новых идей.

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

Этот код неэффективен:
new Float:pos[3];
GetPlayerPos(playerid, pos[0], pos[1], pos[2]);

Вот его представления на языке ассемблера:
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

А вот эффективный код, эквивалентный предыдущему:
new Float:x, Float:y, Float:z;
GetPlayerPos(playerid, x, y , z);

И его ассемблерное представление:
push.c 0 //Making room for  the variables on the stack
push.c 0
push.c 0
push.adr fffffff4 //Pushing the arguments
push.adr fffffff8
push.adr fffffffc

Когда вы хотите присвоить ячейке массива значение, компьютер выполняет следующий алгоритм:

Адрес первого элемена + 4 * Индекс = место, где хранится ячейка массива (Это формула верна только для одноразмерного массива)

После рассчёта адреса элемента, значение в ячейке изменяется.

Это не значит, что вы не должны использовать массивы. Вы должны использовать их с умом. Не используйте массивы там, где можно обойтись обычными переменными.

И, по-моему мнению, переменные x, y, z ещё и добавляют читаемости коду, нежели массив pos[3].

Тесты:
Массив (10 присваиваний):2444,2448,2473
Обычная переменная (10 присваиваний):972,975,963

Код от тестов: http://pastebin.com/aMkNtaC2
Обычные переменные быстрее масиивов почти 2.5 раз.

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

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

if(funcidx("OnPlayerEatBanana") == -1)

Скорее всего вам не нужно это. Если вы знаете название функции заранее, намного проще использовать директивы пре-процессора, чтобы проверить её сущестование.
#if defined OnPlayerEatBanana
//OnPlayerEatBanana has been declared
#endif



if(CallLocalFunction("OnPlayerEatBanana","ii",playerid,bananaid"
))

Вы можете заменит на: 
#if defined OnPlayerEatBanana
if(OnPlayerEatBanana(playerid,bananaid))
#else
//Функция не объявлена
#endif


Тесты: (1 public без аргументов)
Вызов напрямую:204,226,218
CallLocalFunction:1112,1097,1001


Пожалуйста, поймите, что это лишь лучшие условия для CallLocalFunction. На деле CallLocalFunction будет всё медленнее, с увеличением количества public'ов.

3. Нативный код быстрее PAWN кода
Воздержитесь от создания функций на PAWN, если есть нативные функции (или используйте в комбинации с нативными).

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

stock strcpy(dest[], src[], sz=sizeof(dest))
{
     dest[0] = 0;
     return strcat(dest,src,sz); //Notice that I have used strcat instead of writing my own loops
}


Тесты:
Strcpy, реализованный циклом против нативного strcat

Вот две эквивалентные функции strcpy
http://pastebin.com/Y7RJ21tw

Нативная функция:697,700,718,705
Написанная на PAWN функция:5484,5422,5507,5562

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

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

Вариант 1:
for(new i = 0;i <= GetPlayerPoolSize();i++) {}

Вариант 2:в
for(new i = 0,j = GetPlayerPoolSize();i <= j;i++) {}

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

Тесты:
С оптимизацией:1102,1080,1069,1091
Без неё:2374,2359,2429,2364

Код для теста:http://pastebin.com/SLZDGRG4

Скорость неоптимизированного варианта зависит от функции, которую вы используете. Так, вот этот вариант будет работать куда медленне, чем тестовый: 

for(new i = 0; i < CallRemoteFunction("GetPlayersInTeam", "i", TEAM_ID); i++) 
{
  
}


5. Способ присвоения множеству переменных одного значения & использование memset
Вариант 1:
x = abc;
y = abc;
z = abc;

Вариант 2:
x = 
y = 
z = abc;

Какой код быстрее, по-вашему?

В первом варианте:
load.pri c ;Get abc
stor.pri 8 ;Store it in X
break	; 20
load.pri c ;Get abc
stor.pri 4 ;Store it in Y
break	; 34
load.pri c ;Get abc
stor.pri 0 ;Store it in Z


Во втором варианте:
load.pri c ;Get abc
stor.pri 0 ;Store in X
stor.pri 4 ;Store in Y
stor.pri 8 ;Store in Z

Видите разницу? В первом коде мы видим кучу бесполезных инструкций, который получают значение abc снова и снова, в то время как во втором коде эта инструкция выполняется единожды.

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

Тесты:
Использование memset на трёхмерном массиве из 100 элементов:363,367,372
Присвоение значений тому же массиву циклом:6662,6642,6687

6. Декларируйте локальные переменные там, где они нужны

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

Плохой код:
public OnPlayerDoSomething(playerid)
{
      new actionid = GetPlayerAction(playerid), pee_id, peed_on_whome, amount_of_pee;
      if(actionid == PLAYER_PEE)
      {
}
}


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

Если вы освоили мои предыдущие советы, вы должны знать, что когда мы создаем локальную перменную, компилятор создаёт определённую место для неё в стэке и инициализирует там переменную со значением 0.

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

7. Упрощайте математику, избегайте ресурсозатратные операрации

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

Вот классический пример, разгружающий машину:
new Float:x,Float:y,Float:z;
GetPlayerVelocity(playerid,x,y,z);
if(floatsqrt( (x*x) + (y*y) + (z*z)) > 5.0)

>>
new Float:x,Float:y,Float:z;
GetPlayerVelocity(playerid,x,y,z);
if( ((x*x) + (y*y) + (z*z)) > 25.0)

Вы заметили перемену?
Я возвёл в квадрат обе части неравенства и избавился от медленной функции 'floatsqrt'

Вот ещё пример:
for(new i = 0, j = GetTickCount(); i < 10; i++)
{
     if( j - LastTick[i] > MAX_TIME_ALLOWED)
     {
}
}

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

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

8. memcpy, strfind, и прочие работают и с массивами

Собственно, строки и массивы - это одно и тоже. Единственная разница в том, что строки кончаются символом null(\0), а обычные массивы - нет.

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];
}

А вот код, который делает то же самое:
memcpy(PlayerArray[playerid], DefaultPlayerArray, 0, sizeof(DefaultPlayerArray)*4, sizeof(PlayerArray[]));



А протестировал эти два варианта, и вот что получилось:
Версия с циклом:
4286ms
4309ms
4410ms

memcpy:
60ms
62ms
60ms

Также, вы можете использовать strfind, strmid и много других строковых функций с массивами. Единственная проблема в том, что если строковая найдёт 0 в массиве, она прекратит работу, потому что '0' означает '\0'(конец строки).

9. Использование CallRemoteFunction стоит того?
Прим. Переводчика: в русском комьюнити, из той части, которую я знаю, мало кто использует античиты в скриптах, как и сами скрипты. Автор же не ставит под сомнение, что античит у всех находится в скриптах. Главный смысл этого - не используйте CallRemoteFunction (:

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

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

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

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

10. Множественное обращение к элементам массива

Давайте посмотрим пример, чтобы понять о чём мы здесь говорим:
new val = value[x][y][z];
for(new i = 50; i != -1; --i) Arr[i] = val;

>>
for(new i = 50; i != -1; --i) Arr[i] = value[x][y][z];

Что, по вашему мнению, быстрее?

Первый вариант быстрее, помните совет №1? Если да, то вы знаете, что просчёт адреса элемента массива занимает время. Во втором варианте просчёт адреса происходит каждый раз, когда мы копируем значение в Arr, в первом варианте мы рассчитываем адрес лишь однажды.

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

Тесты:
Вариант 1:2280,2330,2350
Вариант 2:8008,8183,8147


11. Не смешиваете числа с плавающей точкой и целые числа в одном выражении.
от Mauzen


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

Никогда не смешивайте float и integer(если только вы не получите warning о несовпадении тегов). Старайтесь всегда использовать одинаковые типы данных в выражениях.

Например:
new Float:result = 2.0 + 1;
// Скомпилируется как:
new Float:result = 2.0 + float(1);
// Что медленнее, чем:
new Float:result = 2.0 + 1.0;


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)


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

12. Не обязательно использовать streamer
Это вошло в привычку для каждого: использовать стример, даже если им нужно всего 10-20 иконок на карте, всего 50 объектов и т.д.

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

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

Когда вы используете Streamer функции, пишите CreateDynamicObject, streamer не создаёт объекта, он просто добавляет данные об объекте(х, у, z,….) в базу данных объектов. Каждый определённый период времени, он проверяет все объекты в базу данных и проверяет, есть ли поблизости игрок, и создаёт объект, если нужно.

Вам действительно нужен streamer, если у вас менее 1000 объектов? Вы нуждаетесь в нём?
НЕТ!

Если вы уверены, что не превысите лимитов SAMP, не используйте streamer.

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

Это не составит сложности, если вы делаете это с умом.

Вот пример этого:
#define CreateDynamicObject CreateObject

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

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


Авторы:
Yashas 
Mauzen за совет #11

Оригинал:
New Code Optimizations
Автор перевода:
AloneAmigo



Копирование на другие ресурсы без разрешения переводчика и указания авторов запрещено.

Сообщение отредактировал AloneAmigo: 23 Февраль 2016 - 13:26

18

#2
Пользователь офлайн   Sound 

  • Эксперт
  • Вставить ник
  • Раскрыть информацию
Круто)
Перечитаю еще несколько раз
0

#3
Пользователь офлайн   Romzes 

  • © Pawn-Wiki.Ru
  • Вставить ник
  • Раскрыть информацию
Очень полезно, лови + за перевод.
0

#4
Пользователь офлайн   Slava Isakov 

  • Местный
  • Вставить ник
  • Раскрыть информацию
В закладку добавил :D
0

#5
Пользователь офлайн   Эдик 

  • Пользователь
  • Вставить ник
  • Раскрыть информацию
Lol а я использовал Float:pos[3];
Окей, щас буду знать! С меня +
0

#6
Пользователь офлайн   PHARA_OH 

  • Эксперт
  • Вставить ник
  • Раскрыть информацию
Отличная тема, если нужна будет в будущем помощь с переводом то пиши, помогу :wink:
0

#7
Пользователь офлайн   _54REGION_ 

  • Эксперт
  • Вставить ник
  • Раскрыть информацию
new Float:pos[3];
GetPlayerPos(playerid, pos[0], pos[1], pos[2]);
Я наоборот старался так делать даже что было как надо переделал на как не надо вчера все переделал как ты сказал нагрузка даже на пару % меньше стала при запуске 2,5 % при онлайне 30 в районе 6 %до этого могла и до 8% подняться и вообще это норм нагрузка? Хотя думаю зависит от хостинга,пользуюсь хостингом 40коп.слот)
Подскажите у кого какая нагрузка хочу сравнить)

Сообщение отредактировал _54REGION_: 19 Февраль 2018 - 09:16

0

#8
Пользователь офлайн   DeimoS 

  • Evil Scripter
  • Вставить ник
  • Раскрыть информацию

Просмотр сообщения_54REGION_ (19 Февраль 2018 - 09:14) писал:

Нажмите сюда, чтобы прочитать это сообщение. [Показать]

Если у тебя на 2% нагрузка упала только от того, что ты массив из трех ячеек перевёл в переменные (а разница между обращением к ячейке массива и обращением к переменной равна, примерно, 0,000000065 мс, судя по одним тестам, что делались на другом сайте), то что же у тебя там за говнокод такой страшный? :blink:

Сообщение отредактировал DeimoS: 19 Февраль 2018 - 10:35

0

#9
Пользователь офлайн   _54REGION_ 

  • Эксперт
  • Вставить ник
  • Раскрыть информацию
Не от 3 ячеек ,ты не подумал что она может быть не одна в моде?
0

#10
Пользователь офлайн   DeimoS 

  • Evil Scripter
  • Вставить ник
  • Раскрыть информацию

Просмотр сообщения_54REGION_ (19 Февраль 2018 - 12:43) писал:

Не от 3 ячеек ,ты не подумал что она может быть не одна в моде?


Это ты меня не так понял.
Хоть у тебя будет несколько тысяч строк с обращением к массивам по всему моду, они никак не будут нагружать процессор на 2%. И либо у тебя там лютейший говнокод, либо ты сам не понимаешь что сделал
0

Поделиться темой:


Страница 1 из 1
  • Вы не можете создать новую тему
  • Вы не можете ответить в тему

1 человек читают эту тему
0 пользователей, 1 гостей, 0 скрытых пользователей