Оригинал
Команда Gridlock продолжает усердно бороться с лагом в больших битвах и оптимизировать устаревший код игры. В конце апреля мы активировали на сервере Tranquility ряд мер, направленных на повышение быстродействия; несколько недель мы измеряли результаты, и, наконец, настала пора рассказать о них. Первыми о них услышал Совет игроков в ходе недавнего визита, а теперь о них узнаете и вы. Так что внимание, вас ждут графики и технический жаргон.
Для начала график (все любят графики!):

На нем показано, сколько ресурсов процессора используется при обработке действий одного пользователя (в просторечии «CPU-на-игрока»); это среднее значение по всем серверам Tranquility за восемь недель. Хотя мы не используем в данном случае какие-то определенные единицы измерения, это сравнение вполне корректно, если конфигурация самих серверов остается неизменной. Чем ниже эта величина, тем лучше. Красным цветом отмечены линии тренда до и после изменений; синим ― даты, когда эти изменения были активированы на сервере. Подписи под линиями ― это названия флагов, которые мы использовали в коде (эта статья основана на отчете, написанном мной для коллег, поэтому я постараюсь придерживаться аналогичной терминологии).
Каждый флаг активирует определенный участок оптимизированного кода без необходимости устанавливать дополнительный патч на Tranquility. Это также позволяет нам «включать» флаги лишь на определенных серверах и следить за возникновением нежелательных последствий до их активации во всей игре.
Флаги
26 апреля мы активировали серверный флаг 'ballparkUsesInventorySelfLocal'. После этого изменения величина «CPU-на-игрока» уменьшилась примерно на 8%. Это более чем хороший результат для такого небольшого изменения (с точки зрения объема переписанного кода). Конечно, потребовалось гораздо больше времени усилий, чтобы понять, что именно надо изменить, и протестировать эти изменения.
2 мая мы активировали серверный флаг 'crimewatchUsesInventorySelfLocal'. Это не сильно повлияло на величину «CPU-на-игрока», но мы и не ожидали иного результата: повышение быстродействия в данном случае заметно лишь в ходе больших боев в «лоу-секе», и на общие результаты измерений оно значительно не влияет.
Если все, что вы хотите знать ― продолжаем ли мы все еще войну с лагом, то можете не читать дальше; на приведенном выше графике содержится вся необходимая информация. Серверные «хомячки» стали на 8% счастливее. Если же вам интересно, как именно мы этого добились, то я с удовольствием вам об этом расскажу.
Моникер ― что это такое
Оба описанных выше изменения имеют схожую природу: замена моникера связанного объекта на прямую ссылку на этот объект. Что это значит? Внимание на доску. Сейчас я расскажу о некоторых принципах программирования ― а потом объясню, почему иногда лучше не брать их в расчет.
Основной способ коммуникации между серверными компонентами ― моникеры и связанные объекты. Моникер ― это указатель на связанный объект. Связанный объект «представляет» компонент и ведет учет количества моникеров, которые на него указывают. По сути дела это вариант реализациишаблона Proxy.
Ситуация становится более интересной, если учесть, что моникер и связанный объект могут обрабатываться разными процессами ― они даже могут находиться на разных серверах (в настоящий момент в состав Tranquility входит примерно 200 серверных «узлов»). По сути дела они могут находиться на разных континентах ― игровой клиент EVE использует те же инструменты для взаимодействия с находящимися на наших серверах объектах. Те из вас, кто занимается программированием, могут видеть, что это реализация механизма RPC. Это позволяет нам обрабатывать различные логические процессы на разных «узлах», используя интерфейс, практически идентичный тому, что используется на каждом узле. Но использование моникеров имеет и отрицательную сторону ― каждый доступ к моникеру требует проведения нескольких дополнительных проверок по сравнению со стандартным вызовом функции. Это обусловлено дополнительными функциями, которые выполняют моникеры ― управление «жизненными циклами» (в случаях, если сервер на другом конце соединения выходит из строя, или один из объектов уничтожается), синхронизацией вызовов (например, к одному связанному объекту не должно поступать два вызова одновременно) и контролем доступа для каждой функции (например, каждый пользователь может вызывать функцию А, но функция Б доступна только гейм-мастерам).

На приведенной выше диаграмме показан вымышленный объект 'ServiceObject', к которому пытаются получить доступ два пользовательских объекта. Каждый сеанс доступа осуществляется через моникер; входящие соединения обрабатываются интерфейсом BoundObject. Черные стрелки ― это логические связи, которые могут находиться в одном пространстве памяти, на двух компьютерах в одной локальной сети или даже на разных концах интернета.
Как все это работает
Каждая звездная система в рамках сервера состоит из нескольких взаимосвязанных компонентов. В частности, три компонента, о которых пойдет речь ниже, называются Ballpark, CrimewatchLocation и InventoryLocation; в каждой звездной системе существует один экземпляр каждого из них. Ballparkотвечает за объекты в космосе (это включает в себя работу физического движка Destiny, отправку обновленной информации игровым клиентам, прыжки через врата и многое другое). CrimewatchLocationотвечает за флаги агрессии, применение правил войн между корпорациями, сообщения о потерях и появление кораблей CONCORD. InventoryLocationследит за тем, где находятся объекты, и выступает в роли «посредника» между сервером и базой данных объектов.
Доступ к InventoryLocation конкретной звездной системы с другого «узла» осуществляется через моникер (при условии, что нам известен идентификатор узла, и у нас есть соответствующие права доступа). Системы Ballpark и CrimewatchLocation также работали по схожему принципу и взаимодействовали с InventoryLocation через моникер.

На этой диаграмме показана связь между системами Ballpark, CrimewatchLocation и InventoryLocation до и после изменения. В первом случае на соединения моникеров накладывались ограничения: они должны были обрабатываться одним и тем же процессом из-за зависимости от других объектов.
Здорово. На первый взгляд может показаться, что правильное решение ― вынести каждый компонент на отдельный «узел» и воспользоваться преимуществами, предоставляемые параллельной обработкой данных. Но для каждой звездной системы эти системы всегда существовали на одном и том же «узле»; со временем они начали использовать другие общие компоненты (не моникеры) и буквально «срослись» друг с другом. Поэтому их разделение ― нетривиальная задача: Ballpark, CrimewatchLocation и InventoryLocation не только обзавелись общими органами, но и постоянно общаются между собой. Время, затрачиваемое на коммуникации между этими системами, не позволяет получить значительное преимущество от параллельной обработки данных, не переписав значительную часть серверной архитектуры.
Рано или поздно нам все равно придется это сделать, но в настоящий момент мы можем улучшить ситуацию, реализовав гораздо менее масштабные изменения.
Исправляем ситуацию
Итак, мы имеем дело с тремя компонентами, общающимися между собой с помощью RPC – но они всегда находятся «в одном пространстве». После того, как я убедился, что использование моникеров заметно снижает быстродействие в этом случае, я начал избавляться от них. Замена моникера на прямую ссылку ― тривиальная задача; ведь вся необходимая функциональность уже реализована.
Вот в чем заключалось изменение:
До:
В момент инициализации:
# Get the moniker to an inventory location:
inv = GetInventoryMoniker(solarsystemID)
В ходе выполнения программы:
# Use the inventory to get stuff from the DB:
item = inv.SelectItem(itemID)
После:
В момент инициализации:
# Get the direct reference to an inventory location:
inv = GetInventoryMoniker(solarsystemID).GetSelfLocal()
В ходе выполнения программы:
# Use the inventory to get stuff from the DB:
item = inv.SelectItem(itemID)
Вот и все. Пятнадцать символов, которые «выключают» часть кода и сокращают использование ресурсов процессора на 8%. Неплохо, а?
Исправляем исправления
Но это не все! Помните, я упоминал, что моникер не только обеспечивает работу механизма RPC, но и делает кое-какие другие полезные вещи ― например, управляет «жизненными циклами» в системе путем подсчета ссылок? Связанные объекты существуют лишь пока существуют ссылающиеся на них моникеры; после исчезновения последнего моникера удаляется и связанный объект. На эту проблему мы и натолкнулись в ходе тестирования: выяснилось, что моникер компонента CrimewatchLocation, ссылающийся на InventoryLocation, ― единственное, что «поддерживало жизнь» этого компонента. Это привело к очень странным ошибкам. Например, звездная система работала нормально, пока для какого-либо игрока в ней был задан флаг агрессии, но как только компонент CrimewatchLocation оставался без дела, работа InventoryLocation прекращалась ― хотя компонент Ballpark продолжал ссылаться на нее.
Как только мы устранили эту проблему, мы провели ряд массовых тестов, активировав соответствующие флаги; другие потенциальные проблемы мы нашли в ходе дальнейшего тестирования на сервере Singularity.
В завершение
Как только тестирование было завершено, мы реализовали эти изменения на сервере Tranquility в «спящем режиме». В конце апреля выдалась спокойная неделя (без установки патчей), и эти изменения были активированы ― 26 апреля мы включили соответствующий флаг для компонента Ballpark, а 2 мая ― для CrimewatchLocation. Это время было выбрано не случайно ― в этот период у нас была возможность спокойно измерить полученные преимущества (и установить возможные проблемы) и при необходимости выключить один флаг или оба.
После нескольких дней на Tranquility никаких проблем обнаружено не было; несколько недель спустя мы собрали достаточно данных, чтобы утверждать, что 30 дополнительных строк кода (не считая исправления некоторых ошибок, связанных с «жизненными циклами») позволили в среднем повысить быстродействие на 8%. Неплохо, а?
Эта статья рассказывает лишь о части работы, которой занимается команда Gridlock. Мы также организуем массовое тестирование, изучаем реализуемые другими командами нововведения, чтобы убедиться, что они не оказывают отрицательного эффекта на быстродействие игры, и работаем над более масштабными проектами. Среди них ― перенос звездных систем на отдельные сервера в режиме реального времени (для масштабных боев) и проект TimeDilation.
- CCP Atlas
Сообщение отредактировал Tandi: 16 June 2011 - 20:46