PDA

Просмотр полной версии : [Урок] Оптимизация | Индивидуальные таймеры



XemyL
11.04.2014, 18:29
Добрый день, пользователи Pro-Pawn.

В этом уроке я хочу донести до вас довольно значимый способ оптимизации мода, который известен далеко не всем. Но сразу предупреждаю, если вы очень слабый скриптер, не читайте дальше и не пишите ничего в этой теме.
Итак, таймеры. Многие считают что использовать кучу таймеров в моде - ужасно плохо, но это не так, если использовать их разумно. Нагружают сервер действия, срабатываемые в таймере, а не сами таймеры (совсем немного). Поэтому 1000 разумно сделанных таймеров будут полезнее чем 5 кое-как сделанных. Если вы это понимаете - идём дальше, иначе закрывайте тему.

Почти в каждом моде есть таймер, допустим на 1 секунду. В этом таймере есть цикл на всех игроков сервера, где с ними производятся действия. Например:


public Timer1Second()
{
foreach(...)
{
// Действия
}
return 1;
}

Именно из за таких таймеров серверы с большим онлайном начинают испытывать лаги и ищут более мощный хостинг. Всё дело в том, что одно срабатывание из foreach (для одного игрока) проходит например за 0.25 - 1 мс (в зависимости от оптимизации и мощности процессора), что впрочем то не так уж и страшно. Но когда игроков 200, выполнение таймера занимает уже от 50 мс до 200 мс каждую секунду и пока весь таймер не закончит своё действие, сервер своими делами не займётся (синхронизация игроков и т.п.), поэтому в эти 200 мс сервер тупо висит - вот она причина лагов. Теперь давайте разберём как избавиться от этого и не мешать серверу заниматься необходимыми для работы сервера делами.

1) Убрать из ВСЕХ таймеров такие циклы с игроками.
2) Объявить глобальный массив (ко всем new):

new PlayerTimerID[MAX_PLAYERS];
3) Ко всем forward:

forward PlayerUpdate(playerid);
4) В OnPlayerConnect:

PlayerTimerID[playerid] = SetTimerEx("PlayerUpdate", 250, 1, "d", playerid);
5) В OnPlayerDisconnect:

KillTimer(PlayerTimerID[playerid]);
6) В конце мода:

public PlayerUpdate(playerid)
{
return 1;
}

В итоге мы получим индивидуальный таймер для каждого игрока, да, их будет 1000 при максимальном онлайне, но ничего страшного - серверу только лучше. Теперь все действия с игроком нужно использовать в PlayerUpdate (он срабатывает каждые 250 мс). Отдельные таймеры на 1 сек и т.п. создавать не нужно, просто подсчитывайте количество срабатываний PlayerUpdate (1 сек = 4 срабатывания) и выполняйте нужные действия.

И что же нам это всё даст? Когда сервер выполнял цикл, он выполнял его полностью на всех игроков и пока не закончил - другими делами не занимался. В нашем случае для каждого игрока идут отдельные таймеры, срабатывающие в разное время, а не сразу все. Поэтому между срабатываниями таймеров, пусть оно будет даже 0,01 мс, но сервер в это время займётся своими делами и ему хватит этого времени, а значит сервер не будет останавливаться в ожидании.

На этом всё, надеюсь что объяснил достаточно понятно. Удачного скриптинга!

DeimoS
11.04.2014, 19:10
А не проще ли 1 глобальный таймер в 500 мс + деление MAX_PLAYERS на несколько тактов (например: сначала обрабатываем чётные ID, а потом - нечётные)? Если всё эти подкрепить системой, которая используется в foreach (чтоб цикл пробегался только по тем игрокам, кто онлайн), выйдет 1 таймер и не такая уж и большая нагрузка. Да и не обязательно делить только на чётные и нечётные. Можно и на большие куски разделить всех игроков. Выйдет, по моему, гораздо удобнее.
Хотя и этот способ хороший. Но пока оба способа не будут проверены на реальном сервере с онлайном хотя бы в 500 человек, говорить что-то о том, какой из них лучше, не стоит, как я считаю :)

XemyL
11.04.2014, 19:37
А не проще ли 1 глобальный таймер в 500 мс + деление MAX_PLAYERS на несколько тактов (например: сначала обрабатываем чётные ID, а потом - нечётные)? Если всё эти подкрепить системой, которая используется в foreach (чтоб цикл пробегался только по тем игрокам, кто онлайн), выйдет 1 таймер и не такая уж и большая нагрузка. Да и не обязательно делить только на чётные и нечётные. Можно и на большие куски разделить всех игроков. Выйдет, по моему, гораздо удобнее.
Хотя и этот способ хороший. Но пока оба способа не будут проверены на реальном сервере с онлайном хотя бы в 500 человек, говорить что-то о том, какой из них лучше, не стоит, как я считаю :)
А я считаю что мой способ наоборот удобнее. Метод был протестирован на сервере с онлайном 300 человек. У них тогда ещё были огромные лаги, а после того как я им перевёл на такие таймеры, все лаги прошли. Но онлайн они больше 300 не набрали, увы...

Jeff_Monson
12.04.2014, 10:59
Реально удобно, сам так пользуюсь с того момента как вы создали в другом портале тему.

Вот вопрос а проверки античиты, и прочие туда в пихать или лучше в глобальный?

В глобальном у меня только gzcheck и mzcheck античиты в индивидуальном таймере.

XemyL
12.04.2014, 12:45
Реально удобно, сам так пользуюсь с того момента как вы создали в другом портале тему.

Вот вопрос а проверки античиты, и прочие туда в пихать или лучше в глобальный?

В глобальном у меня только gzcheck и mzcheck античиты в индивидуальном таймере.

Античиты тоже в индивидуальные.

KShaddix
13.04.2014, 23:02
Раньше я видел лишь утверждения, что таймеров должно быть не больше 10. В противном случае на сервере будут лаги. lol.
Доверюсь тебе, сделаю по твоему способу.

DeimoS
14.04.2014, 14:38
Раньше я видел лишь утверждения, что таймеров должно быть не больше 10. В противном случае на сервере будут лаги. lol.
Доверюсь тебе, сделаю по твоему способу.
...

Нагрузку даёт функция, вызываемая таймером. Сам же таймер является своеобразной временной меткой, по которой мод вызывает ту или иную функцию. Он лишь записывается в память сервера, но никак не нагружает ЦП. Проверить это легко. Вставляем в new.pwn

new Timer;
for(new i; i < 5000; ++i) Timer = SetTimer("_", 1000, true);
printf("Таймеров запущено - %d",Timer);
и запускаем сервер. Открываем диспетчер задач, ищем процесс сервера (samp-server.exe) и делаем скрин. Ну и смотрим на то, как нагружает сервер ЦП/заходим на сервер в ожидании лагов.
После закрываем его, открываем мод и убираем цикл с созданием таймера. Вновь запускаем сервер и сверяем данные со скрина с данными в диспетчере задач.

KShaddix
15.04.2014, 09:18
Сам таймер не вызывает лаги.Даже SetTimerEx,просто нужно умно их использовать.
Я уже понял, прочитав верхний пост. Сейчас имею в виду, что раньше был убеждён в обратном.

Kenny_Dalglish
04.05.2014, 03:03
Урок то хороший вот только не понял как создавать таймеры на 1-10 секунд. Пример покажите...

DeimoS
04.05.2014, 07:06
Эмм

SetTimerEx("PlayerUpdate", 1000*10, 1, "d", playerid);
таймер на 10 секунд, не?

XemyL
04.05.2014, 13:05
Эмм

SetTimerEx("PlayerUpdate", 1000*10, 1, "d", playerid);
таймер на 10 секунд, не?

Руки прочь от темы!


new Timer1Second;

public PlayerUpdate(playerid)
{
// Какой-то код
if(Timer1Second == 4)
{
// Код, который должен выполняться каждую секунду
Timer1Second = 0;
}
else Timer1Second++;
return 1;
}

Kenny_Dalglish
04.05.2014, 16:57
Руки прочь от темы!


new Timer1Second;

public PlayerUpdate(playerid)
{
// Какой-то код
if(Timer1Second == 4)
{
// Код, который должен выполняться каждую секунду
Timer1Second = 0;
}
else Timer1Second++;
return 1;
}
Во теперь понял 4 такта = 1 секе.

- - - Добавлено - - -

Ну тогда ещё вопрос, теперь из всех остальных таймеров мы перемешаем в единый таймер и убераем циклы?

XemyL
05.05.2014, 01:26
Ну тогда ещё вопрос, теперь из всех остальных таймеров мы перемешаем в единый таймер и убераем циклы?

Именно. Только циклы на игроков!

underwoker
06.05.2014, 00:04
Возможно скоро начну перевод на эти самые таймеры. Но все таки пока недоверяю :)

Salvacore
06.05.2014, 01:12
Возможно скоро начну перевод на эти самые таймеры. Но все таки пока недоверяю :)
Отлично всё, советую.

DeimoS
06.05.2014, 05:35
Всё же хотелось бы увидеть реальный сервер, который с онлайном в 200-300 человек и на котором видна польза от этих лагов (а чтоб она была видна, нужен подобный сервер, но без этой системы). Не думаю, что различия будут существенны, если говнокод таймером вызывается. Pawn то однопоточен будет и он плевал на ваши принципы в принципе.


Руки прочь от темы!

Ах да, перепутал твои темы. Это же не инклюд :c

Caypen
18.05.2014, 14:13
Руки прочь от темы!


new Timer1Second;

public PlayerUpdate(playerid)
{
// Какой-то код
if(Timer1Second == 4)
{
// Код, который должен выполняться каждую секунду
Timer1Second = 0;
}
else Timer1Second++;
return 1;
}

В таком случае прибавили игроку с id 0 к Timer1Second +1, тут у игрока с ид 2 сработал этот кэлбек и ему прибавляем уже не с нуля а с 1+1, баг получается)

underwoker
01.06.2014, 02:35
В таком случае прибавили игроку с id 0 к Timer1Second +1, тут у игрока с ид 2 сработал этот кэлбек и ему прибавляем уже не с нуля а с 1+1, баг получается)
Ты имеешь ввиду что Timer1Second прибавляется не для каждого игрока по отдельности?

DeimoS
01.06.2014, 08:35
Ты имеешь ввиду что Timer1Second прибавляется не для каждого игрока по отдельности?

Именно это он и говорит. Переменная то глобальная. И не массив.

DmX
06.06.2014, 11:13
Руки прочь от темы!


new Timer1Second;

public PlayerUpdate(playerid)
{
// Какой-то код
if(Timer1Second == 4)
{
// Код, который должен выполняться каждую секунду
Timer1Second = 0;
}
else Timer1Second++;
return 1;
}


new Timer1Second = 4;

public PlayerUpdate(playerid)
{
// Какой-то код
if(Timer1Second == 0)
{
// Код, который должен выполняться каждую секунду
Timer1Second = 4;
}
else Timer1Second--;
return 1;
}
Лучше отнимать)

Daniel_Cortez
06.06.2014, 11:58
Лучше отнимать)

Смелое предположение. Интересно, и чем же лучше?

DmX
06.06.2014, 14:16
Смелое предположение. Интересно, и чем же лучше?

Где то читал, еще давно, что переменные отнимаются быстрей чем прибавляются)

DeimoS
07.06.2014, 06:57
Где то читал, еще давно, что переменные отнимаются быстрей чем прибавляются)

Лол, вы, скорее всего, говорите про преинкрементацию и постинкрементацию, а не про отнять/прибавить. И если первое никакой роли в циклах не играет, то второе... Вряд ли разница вообще есть, ибо кака машине разница: и в случае прибавки, и в случае вычитания ей нужно просто изменить значение переменной на новое. Для неё это просто изменение значения. Не знает она таких понятий, как "прибавить" и "отнять". А если и знает, скорость обработки, в случае с "++", настолько мала, что "--" вряд ли сможет дать какой-то прирост, который можно будет измерить, не проведя пару миллиардов итераций в цикле

Daniel_Cortez
07.06.2014, 07:20
скорость обработки, в случае с "++", настолько мала, что "--" вряд ли сможет дать какой-то прирост, который можно будет измерить, не проведя пару миллиардов итераций в цикле

Тут даже 2 миллиада итераций не помогут. Если откомпилировать байткод AMX в машинный код, используя JIT, операции inc и dec будут выполняться с одинаковой скоростью: обе операции настолько просты, что уложатся всего в 1 такт. То же самое будет и при обычной интерпретации кода без JIT.
Поэтому утверждать, что инкремент быстрее декремента, просто глупо. Разве что для очередного срача на говно-инфо сойдёт.

DmX
07.06.2014, 20:36
Тут даже 2 миллиада итераций не помогут. Если откомпилировать байткод AMX в машинный код, используя JIT, операции inc и dec будут выполняться с одинаковой скоростью: обе операции настолько просты, что уложатся всего в 1 такт. То же самое будет и при обычной интерпретации кода без JIT.
Поэтому утверждать, что инкремент быстрее декремента, просто глупо. Разве что для очередного срача на говно-инфо сойдёт.
Спасибо, буду знать)

underwoker
08.06.2014, 04:52
Тут даже 2 миллиада итераций не помогут. Если откомпилировать байткод AMX в машинный код, используя JIT, операции inc и dec будут выполняться с одинаковой скоростью: обе операции настолько просты, что уложатся всего в 1 такт. То же самое будет и при обычной интерпретации кода без JIT.
Поэтому утверждать, что инкремент быстрее декремента, просто глупо. Разве что для очередного срача на говно-инфо сойдёт.
Пойду повешусь от прочитанного. (Мозг сгорел)

Caypen
08.06.2014, 13:07
Пойду повешусь от прочитанного. (Мозг сгорел)

А что он такого сказал?

Salvacore
09.06.2014, 09:53
А что он такого сказал?
Ну типа сложный текст :/

Glant
02.08.2014, 22:59
А если просто использовать OnPlayerUpdate, и там подсчитывать количество выполнений, чтобы выполнять дела например за 1с, 2сек, 5 и т.д
?

Стандартный паблик вместо таймера

Daniel_Cortez
02.08.2014, 23:23
А если просто использовать OnPlayerUpdate, и там подсчитывать количество выполнений, чтобы выполнять дела например за 1с, 2сек, 5 и т.д
?

Стандартный паблик вместо таймера

Этот паблик может и не вызываться, если игрок в AFK.

Desulaid
21.12.2015, 03:10
Всё дело в том, что одно срабатывание из foreach (для одного игрока) проходит например за 0.25 - 1 мс (в зависимости от оптимизации и мощности процессора), что впрочем то не так уж и страшно. Но когда игроков 200, выполнение таймера занимает уже от 50 мс до 200 мс каждую секунду и пока весь таймер не закончит своё действие, сервер своими делами не займётся (синхронизация игроков и т.п.), поэтому в эти 200 мс сервер тупо висит - вот она причина лагов.

Зачем подсчитывать срабатывания, когда можно перезапускать таймер?


new player_timer_id[MAX_PLAYERS];

@OnPlayerUpdate(playerid);
@OnPlayerUpdate(playerid)
{
player_timer_id[playerid] = SetTimerEx("@OnPlayerUpdate", 1_000, false, "i", playerid);
}

public OnPlayerConnect(playerid)
{
player_timer_id[playerid] = SetTimerEx("@OnPlayerUpdate", 1_000, false, "i", playerid);
return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
KillTimer(player_timer_id[playerid]);
return 1;
}

seriu
24.12.2015, 04:22
Зачем подсчитывать срабатывания, когда можно перезапускать таймер?


new player_timer_id[MAX_PLAYERS];

@OnPlayerUpdate(playerid);
@OnPlayerUpdate(playerid)
{
player_timer_id[playerid] = SetTimerEx("@OnPlayerUpdate", 1_000, false, "i", playerid);
}

public OnPlayerConnect(playerid)
{
player_timer_id[playerid] = SetTimerEx("@OnPlayerUpdate", 1_000, false, "i", playerid);
return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
KillTimer(player_timer_id[playerid]);
return 1;
}

Ну допустим, для того что есть код который не должен входить в

new Timer1Second[MAX_PLAYERS];

public OnPlayerUpdate(playerid)
{
// Какой-то код
if(Timer1Second[playerid] == 4)
{
// Код, который должен выполняться каждую секунду
Timer1Second[playerid] = 0;
}
else Timer1Second[playerid]++;
return 1;
}
но как уже и сказали ранее, этот метот не эффективен ибо OnPlayerUpdate не работает когда игрок АФК.

А таймеры XemyL вполне нормальный способ создать второй OnPlayerUpdate и тот будет работать даже если игрок АФК.

Desulaid
24.12.2015, 20:42
Ну допустим, для того что есть код который не должен входить в

new Timer1Second[MAX_PLAYERS];

public OnPlayerUpdate(playerid)
{
// Какой-то код
if(Timer1Second[playerid] == 4)
{
// Код, который должен выполняться каждую секунду
Timer1Second[playerid] = 0;
}
else Timer1Second[playerid]++;
return 1;
}
но как уже и сказали ранее, этот метот не эффективен ибо OnPlayerUpdate не работает когда игрок АФК.

А таймеры XemyL вполне нормальный способ создать второй OnPlayerUpdate и тот будет работать даже если игрок АФК.

@OnPlayerUpdate не OnPlayerUpdate, если присмотреться. А в примере XemyL немного таймеры с BDSM. Подсчет тактов какой-то. :crazy:

RefunQ
04.06.2016, 23:57
Спасибо, толково обьяснил)