01.12.2015, 18:02
Обрабатывать частями лучше, соглашусь, но по другим причинам, и не всегда.
Если вам нужно производить какие-то простые задачи каждую секунду (например, уменьшать сытость игроков), лучшим вариантом будет foreach и один цикл в одном таймере. Это проще, а значит, лучше.
Если речь идет о каких-то комплексных задачах (например, античит, проверяющий игроков по всей строгости), тут дела обстоят иначе. Если вы хотите знать, какой вариант потребует меньше всего процессорного времени, я без колебаний отвечу - с одним циклом и одним таймером, но это еще ничего не значит.
Представим ситуацию, когда на обработку одного игрока уходит 1мс, игроков на сервере 200, один добавленный таймер отнимает 1мс (не будем говорить о количестве итераций в циклах, это практически не скажется на результатах). Обратите внимание, что цифры я взял для примера, на самом деле они могут быть больше (на обработку игроков) или меньше (таймеры обычно кушают меньше времени).
Если мы используем вариант с одним таймером и циклом по всем игрокам, вот сколько времени это займет в секунду:
200 игроков * 1мс + 1мс на таймер = 201мс работы процессора в секунду (грубо говоря, 20.1%)
А если будем давать каждому игроку отдельный таймер:
200 игроков * 1мс + 200 игроков * 1мс на таймер = 400мс процессорного времени в секунду (также грубо говоря 40%) (это НЕ значит, что вариант у 2 раза медленнее, так как числа взяты для примера)
Теперь рассмотрим с другой стороны все это.
В первом варианте сервер будет "висеть" 201мс до тех пор, пока все игроки не будут обработаны, и в это время больше ничего обрабатываться не будет. Грубо говоря, это самый натуральный лаг на 0.2 секунды каждую секунду (и, если не ошибаюсь, такие операции дурно сказываются на точности таймеров сервера). На многопользовательском игровом сервере типа SA-MP я бы это считал недопустимым. Например потому, что параллельно может работать второй таймер с интервалом в 300мс, и из-за первого таймера его интервал будет "прыгать" между 300 и 500+ (это я тоже для примера, но такое возможно).
Второй вариант не отбирает все внимание на себя, поэтому лаги могут быть менее заметны, а прыжки менее значительны, потому что в таком случае нагрузка на сервер будет распределена (практически случайно, ведь таймеры добавляются игрокам при входе, а игроки имеют свойство входить в разные доли секунды). Но минус этого варианта в том, что таймеры не бесплатны (каждый таймер, как я это представляю, добавляет как минимум одну операцию сравнения, чтобы сервер узнал, когда стоит его вызывать). Так как таймеров много, процессор будет больше работать (я не затрагиваю темы использования памяти или выделения ресурсов, только производительность).
Третий вариант с обработкой игроков небольшими пачками в этом случае подойдет лучше (имхо), и я объясню, почему, на примере.
Допустим, обработка одного игрока все так же требует 1мс, таймер кушает 1мс, и вызывается это все раз в секунду для 200 игроков. Также предположим, что на сервере 300 слотов.
Создадим 1 таймер с интервалом запуска в 200мс, в отдельной переменной будем хранить текущий "указатель" на слот игрока. Функция, которая будет вызываться в таймере, будет обрабатывать следующих 60 игроков после указателя, либо начинать остчет с 0, если прошлась по всем игрокам, и каждый раз сохранять указатель.
Таким образом, функция будет вызвана примерно 5 раз в секунду для 60 слотов - в идеале, 300 слотов в секунду, при этом
В секунду будет потреблять (в идеале) 5 вызовов * ( 60 слотов * 1мс на слот (или меньше, если игрок оффлайн) ) + 1мс на таймер = 301мс (в случае прямого перебора всех слотов, т.е. не самый оптимизированный вариант), но со стороны это будет что-то вроде [60мс работает] [140мс делает что-то другое] [60мс работает] [140мс делает что-то другое]...
Как видно, "время лагания" по сравнению с 1 вариантом (200мс) больше чем у 3 раза короче, а значит, лаги менее заметны и нагрузка лучше распределена.
Чтобы еще больше уменьшить лаги, можно сделать интервал меньше - например, 50мс (15 игроков за раз) будут вызывать практически незаметные лаги длительностью 15мс (+время на более частый вызов функции) и почти равномерно распределять нагрузку, но не стоит забывать, что вызов функции и запуск цикла тоже потребляют процессорное время - нужно найти золотую средину.
Касательно того, как в третьем варианте не обрабатывать ненужные слоты (в случае с онлайном 200/300 речь идет о последних 100 слотах, 200-299).
В простом подходе с прямым перебором слотов будет тратиться лишнее время на проверку пустых слотов и циклы, что тоже не есть хорошо. Навскидку я бы предложил решить эту проблему ценой оперативной памяти:
1. Создать глобально массив из n итераторов (не помню, как правильно, в общем iterable из foreach), где n - количество "пачек" игроков (при n=10 мы будем вызывать функцию в цикле с интервалом в 100мс, 10 раз в секунду, при 20 - с интервалом в 50мс)
2. Равномерно распихивать игроков по всем итераторам при входе на сервер (особо не скажется на производительности, так как выполняется единожды)
3. В обработчике, который запускается таймером каждых 1/n секунд, по очереди брать следующий итератор из массива и обрабатывать находящихся там игроков.
4. При выходе игрока из сервера, убирать его ID из итератора.
Я не проверял, что писал, поэтому дико извиняюсь, если есть ошибки или перепутал что, но идею старался объяснить. Если у вас есть подходы получше - с удовольствием послушаю
Если вам нужно производить какие-то простые задачи каждую секунду (например, уменьшать сытость игроков), лучшим вариантом будет foreach и один цикл в одном таймере. Это проще, а значит, лучше.
Если речь идет о каких-то комплексных задачах (например, античит, проверяющий игроков по всей строгости), тут дела обстоят иначе. Если вы хотите знать, какой вариант потребует меньше всего процессорного времени, я без колебаний отвечу - с одним циклом и одним таймером, но это еще ничего не значит.
Представим ситуацию, когда на обработку одного игрока уходит 1мс, игроков на сервере 200, один добавленный таймер отнимает 1мс (не будем говорить о количестве итераций в циклах, это практически не скажется на результатах). Обратите внимание, что цифры я взял для примера, на самом деле они могут быть больше (на обработку игроков) или меньше (таймеры обычно кушают меньше времени).
Если мы используем вариант с одним таймером и циклом по всем игрокам, вот сколько времени это займет в секунду:
200 игроков * 1мс + 1мс на таймер = 201мс работы процессора в секунду (грубо говоря, 20.1%)
А если будем давать каждому игроку отдельный таймер:
200 игроков * 1мс + 200 игроков * 1мс на таймер = 400мс процессорного времени в секунду (также грубо говоря 40%) (это НЕ значит, что вариант у 2 раза медленнее, так как числа взяты для примера)
Теперь рассмотрим с другой стороны все это.
В первом варианте сервер будет "висеть" 201мс до тех пор, пока все игроки не будут обработаны, и в это время больше ничего обрабатываться не будет. Грубо говоря, это самый натуральный лаг на 0.2 секунды каждую секунду (и, если не ошибаюсь, такие операции дурно сказываются на точности таймеров сервера). На многопользовательском игровом сервере типа SA-MP я бы это считал недопустимым. Например потому, что параллельно может работать второй таймер с интервалом в 300мс, и из-за первого таймера его интервал будет "прыгать" между 300 и 500+ (это я тоже для примера, но такое возможно).
Второй вариант не отбирает все внимание на себя, поэтому лаги могут быть менее заметны, а прыжки менее значительны, потому что в таком случае нагрузка на сервер будет распределена (практически случайно, ведь таймеры добавляются игрокам при входе, а игроки имеют свойство входить в разные доли секунды). Но минус этого варианта в том, что таймеры не бесплатны (каждый таймер, как я это представляю, добавляет как минимум одну операцию сравнения, чтобы сервер узнал, когда стоит его вызывать). Так как таймеров много, процессор будет больше работать (я не затрагиваю темы использования памяти или выделения ресурсов, только производительность).
Третий вариант с обработкой игроков небольшими пачками в этом случае подойдет лучше (имхо), и я объясню, почему, на примере.
Допустим, обработка одного игрока все так же требует 1мс, таймер кушает 1мс, и вызывается это все раз в секунду для 200 игроков. Также предположим, что на сервере 300 слотов.
Создадим 1 таймер с интервалом запуска в 200мс, в отдельной переменной будем хранить текущий "указатель" на слот игрока. Функция, которая будет вызываться в таймере, будет обрабатывать следующих 60 игроков после указателя, либо начинать остчет с 0, если прошлась по всем игрокам, и каждый раз сохранять указатель.
Таким образом, функция будет вызвана примерно 5 раз в секунду для 60 слотов - в идеале, 300 слотов в секунду, при этом
В секунду будет потреблять (в идеале) 5 вызовов * ( 60 слотов * 1мс на слот (или меньше, если игрок оффлайн) ) + 1мс на таймер = 301мс (в случае прямого перебора всех слотов, т.е. не самый оптимизированный вариант), но со стороны это будет что-то вроде [60мс работает] [140мс делает что-то другое] [60мс работает] [140мс делает что-то другое]...
Как видно, "время лагания" по сравнению с 1 вариантом (200мс) больше чем у 3 раза короче, а значит, лаги менее заметны и нагрузка лучше распределена.
Чтобы еще больше уменьшить лаги, можно сделать интервал меньше - например, 50мс (15 игроков за раз) будут вызывать практически незаметные лаги длительностью 15мс (+время на более частый вызов функции) и почти равномерно распределять нагрузку, но не стоит забывать, что вызов функции и запуск цикла тоже потребляют процессорное время - нужно найти золотую средину.
Касательно того, как в третьем варианте не обрабатывать ненужные слоты (в случае с онлайном 200/300 речь идет о последних 100 слотах, 200-299).
В простом подходе с прямым перебором слотов будет тратиться лишнее время на проверку пустых слотов и циклы, что тоже не есть хорошо. Навскидку я бы предложил решить эту проблему ценой оперативной памяти:
1. Создать глобально массив из n итераторов (не помню, как правильно, в общем iterable из foreach), где n - количество "пачек" игроков (при n=10 мы будем вызывать функцию в цикле с интервалом в 100мс, 10 раз в секунду, при 20 - с интервалом в 50мс)
2. Равномерно распихивать игроков по всем итераторам при входе на сервер (особо не скажется на производительности, так как выполняется единожды)
3. В обработчике, который запускается таймером каждых 1/n секунд, по очереди брать следующий итератор из массива и обрабатывать находящихся там игроков.
4. При выходе игрока из сервера, убирать его ID из итератора.
Я не проверял, что писал, поэтому дико извиняюсь, если есть ошибки или перепутал что, но идею старался объяснить. Если у вас есть подходы получше - с удовольствием послушаю