▍ Оптимизация роботов (автор: Rseding)
За годы работы над Factorio я профилировал множество файлов сохранений и регулярно встречал сохранёнки, где большая часть времени обновления тратится на логистику и/или строительных дронов. В этом нет ничего нового, но наряду с дронами существуют и дронстанции (в больших количествах).
Типичная фабрика с кучей дронстанций (Roboport)
Дронстанции никогда не были «медленными», но они всегда присутствуют на карте, и у игроков есть мотивация строить их в больших количествах; к тому же, их будет ещё больше в грядущем Space Age, где нужно будет многое делать удалённо. Сохранение, полученное после последней сессии плейтестинга, снова показало, что они отнимают небольшое, но ненулевое количество времени, поэтому я снова задумался о них.
Было бы очень здорово, если бы им не нужно было оставаться активными и обновляться постоянно. Большинство дронстанций ничем не занимается, кроме потребления энергии. Достаточно редко (по сравнению с их сроком жизни) к ним подлетают дроны, которым нужно зарядиться или приземлиться. Но большинство из них на самом деле не выполняет ничего, только расширяет сеть логистики. Поэтому я решил поэкспериментировать: что, если просто отключать их, когда им не нужно делать ничего особенного? Если дрон подлетает и нуждается в зарядке или возникает ещё какая-то редкая ситуация, когда им нужно что-то делать, я буду просто включать их до момента завершения процесса.
Разумеется, в этом были и другие трудности, но в конечном итоге всё получилось.
Благодаря этому в файле сохранения последнего плейтеста время, которое тратится на дронстанции, снизилось с 1 мс до 0,025 мс на тик.
▍ Оптимизация логики радаров (автор: Rseding)
Ранее в этом месяце в списке задач появилась карточка с небольшой фичей: добавить дронстанциям «малое радарное покрытие». В обычной ситуации для этого бы потребовалось пять минут работы, но к карточке было приложено крошечное условие: «сделать это так, чтобы пересекающиеся области не увеличивали затраты производительности».
До этого момента радары как сущность и функции радаров, привязанные к другим элементам (например, к игроку), использовали очень простой принцип: «периодически итеративно обходить их и просить систему карты оставлять их открытыми». Обычно это работало нормально, потому что на карте обычно не бывает множества пересекающихся радаров или игроков. Но теперь нам нужно было, чтобы ту же самую логику выполняли дронстанции — сущность, которую выстраивают в очень плотные кластеры с большой степенью пересечения.
Я решил остановиться на системе регистрации — всё, что хочет раскрыть область карты, просто регистрирует блоки как «оставить открытыми», увеличивая счётчик для блока в системе карты. Пока счётчик больше нуля, блок остаётся видимым. Элементы могут пересекаться как угодно, они просто увеличивают значения счётчиков.
Опция отладки, позволяющая отображать счётчик «оставить открытым».
Если значение счётчика блока больше нуля, он помещается в бакет обновления, и на каждом тике мы циклически обходим один бакет.
Если счётчик блока становится равным нулю, то он удаляется из бакета и перестаёт обновляться.
Сканирование бакетов для отображения блоков (скорость 50%)
Кроме того, благодаря этой новой системе я смог заменить логику сущности радара и других сущностей со свойствами радара так, чтобы они использовали одну общую систему регистрации. Эффект был неожиданным — радары как сущности стали занимать меньше времени обновления, а из-за того, что дронстанции теперь можно использовать как радары, самих радаров требуется не так много.
В другом файле сохранения плейтеста мы повысили общую производительность игры на 3,6%. Учитывая то, что это не должно было дать никакого осязаемого эффекта (я вообще ожидал, что добавление функции радара дронстанциям ухудшит ситуацию), новость нас очень порадовала.
Итак, мы добились успеха, и в версии 2.0 дронстанции имеют небольшой «встроенный» радар с радиусом действия в два блока.
▍ Фонари всегда включены (автор: boskid)
Когда мы ели в ресторане во время недавней офисной LAN-пати, kovarex поделился своей идеей: учитывая, что для фонарей можно устанавливать любой RGB-цвет, можно создавать с их помощью изображения. Единственная проблема заключалась в том, что для подачи питания на фонари нужно было бы размещать большое количество подстанций, из-за чего изображение будет не особо красивым. Я сказал, что эта проблема решается постройкой на космических платформах, ведь на платформах не нужны опоры ЛЭП. Похоже, это его обрадовало, но я решил подождать следующего дня.
Когда настал следующий день плейтестинга, он создал чертёж 100 на 150 с фонарями, которые поместил на космическую платформу. Изображение должно было преобразоваться в RGB-цвета этих фонарей. Однако предстояло решить одну важную проблему: на космической платформе всегда день, поэтому фонари не включаются. Чтобы заставить фонари работать днём, их нужно подключить к логической сети. Мы легко это заметили, потому что время обновления в нашем быстро растущем файле сохранения увеличивалось слишком стремительно.
Один из фонарей принудительно постоянно включён при помощи control behavior
15 тысяч фонарей, включённых при помощи control behavior и обновляемых в каждом тике — это неоптимальная система, поэтому я сделал так, чтобы фонари можно было принудительно включать даже днём.
Фонари, использующие Always ON.
Благодаря этому обновление файла сохранения ускорилось на 1,2 мс, ведь теперь не нужно было подключать фонари к логической сети. Однако изучив время обновления control behavior после внесённого мной изменения, я заметил, что обновление control behavior всё равно слишком долгое. Это меня беспокоило, поэтому нужно было выяснить причины.
▍ Считыватель конвейеров и многопоточность control behavior (автор: boskid)
То, что обновление control behavior по-прежнему выполнялось долго, объяснить было легко. Причина заключалась в созданной мной функции: возможности считывать содержимое всех конвейеров в последовательности (см. FFF-405).
Сам считыватель конвейеров был добавлен примерно по тем же причинам, что и всегда включённые фонари — чтобы снизить количество активных control behavior, потому что для считывания содержимого каждый элемент конвейера необходимо подключать по отдельности. Благодаря возможности считывания содержимого всех конвейеров в последовательности достаточно подключить проводом только один из этих конвейеров, для подсчёта предметов нужно только одно control behavior, а сами транспортировочные линии не нужно разделять на элементы длиной 1 тайл. По сути, добавление считывателя конвейеров позволило существенно снизить затраты на обновление control behavior и затраты на обновление транспортировочных линий, при этом обеспечив новые, ранее недоступные возможности (например, подсчёт предметов на подземных конвейерах).
Проблема считывателя конвейеров в простоте его использования, во время плейтестинга мы очень активно его применяли не только на космических платформах, как предполагалось, но и в других местах.
Считыватель конвейеров используется там, где я этого не ожидал
Могу сказать, что основным саботажником нашего тестирования был Hrusa. Он часто использовал считыватели конвейеров так, что потом разрешить проблемы мы могли только с его участием. Кроме того, на Фулгоре размещаются сундуки активного снабжения, превращающие её в ад логистических роботов: в воздухе постоянно находится более десяти тысяч логистических роботов. Я не могу наказывать Hrusa за то, что он играет в игру, поэтому настала пора оптимизаций. Оптимизацией кода логистических роботов занимался другой разработчик, однако считыватель конвейеров и control behavior были моей задачей.
Считыватель конвейеров устроен крайне просто: каждый тик он должен проверять, какие предметы находятся на конвейерах и сколько их в каждом из стеков, чтобы подсчитать общую сумму.
Оптимизация считывателей выполнялась в несколько итераций. Поворотным моментом для меня стало осознание того, что считыватель — это в основном операция чтения: он просто считывает кучу данных из памяти (содержимое стеков конвейеров) для подсчёта предметов, а в конце создаёт один кадр сигналов, который нужно отправить логической сети. Благодаря такой структуре я мог реализовать многопоточную обработку: множество вычисляемых одновременно считывателей конвейеров не влияет друг на друга, потому что они считывают только содержимое конвейеров, а их выходные данные не используются другими считывателями. Подобную структуру можно заметить и в других местах, например, в дронстанциях, считывающих содержимое логистической сети, арифметических комбинаторах, селекторных комбинаторах и сравнивающих комбинаторах. Все эти системы, за исключением селекторного комбинатора (который больше не может использовать глобальный генератор случайных чисел), достаточно легко можно сделать многопоточными.
После внесения всех этих изменений наш файл сохранения для плейтестинга смог работать примерно на 9,5% быстрее.
Синтетический файл сохранения с 77 тысячами комбинаторов, связанных шестью тысячами логических сетей, работал в 14,9 раз быстрее, при этом стабильно используя CPU на 100% (время бенчмарка снизилось с 131 с до всего 8,2 с).
▍ Неудачная попытка: многопоточное обновление электрических сетей (автор: boskid)
Я часто слышал от пользователей подобную просьбу: сделайте обновление электрических сетей многопоточным.
Мы уже улучшали обновление электрических сетей (FFF-209), однако оно всё равно выполнялось в одном потоке, который по наблюдениям часто выполнялся медленнее остальных. В большинстве случаев сохранёнки содержат всего одну крупную сеть, поэтому многопоточность не даст особого выигрыша. Однако в Space Age есть множество крупных сетей (на разных планетах), поэтому потенциал многопоточности выше.
При внедрении многопоточности первым делом всегда нужно изучить взаимодействие частей системы друг с другом. Это необходимо, потому что игра должна оставаться полностью детерминированной, иначе произойдёт рассинхронизация.
Поначалу может показаться, что это довольно просто: каждый поток работает с отдельной электросетью, и на этом всё. Однако это не так, ведь существует игровая механика, которая всё усложняет: возможность получения питания сущностью от нескольких электрических сетей.
Один из случаев, когда электрические сети зависимы друг от друга
В этом примере при работе нефтеперерабатывающего завода его хранилище энергии разряжается, а электросети должны снова его зарядить. Существует два сценария возможных действий:
- Сначала обновляется левая электросеть, заряжая завод от парового двигателя; бойлер сжигает топливо и активируется манипулятор.
- Сначала обновляется правая электросеть, заряжая завод от солнечных панелей (в этом случае бойлер не начинает работу).
Из-за этого сети зависят друг от друга и должны обновляться в одном потоке.
Выявив все подобные случаи (например, выключатель питания, объединяющий несколько сетей), я смог определить, что должно содержаться в группе обновления. Если две электросети имеют хотя бы одну сущность, питаемую от обеих сетей, то эти сети должны находиться в одной группе обновления и обновляться одним потоком, благодаря чему не возникнет рассинхронизаций.
После этого я был готов к тому, чтобы начать измерения…
▍ Результаты
И здесь идея потерпела полный провал. В сохранённом файле плейтестинга было четыре крупные полностью независимые электросети, потому что они находились на разных планетах, однако время обновления сетей осталось таким же, а использование CPU существенно увеличилось.
Как оказалось, обновление электросети выполняет не так много действий: просто считывает две переменные, производит одно-два сложения и переходит к следующей сущности. Оно ограничено пропускной способностью памяти, и при наличии множества потоков, считывающих эти данные из памяти, процессор просто не может считывать данные быстрее.
В данном случае после реализации многопоточности память ждал уже не один поток, а все потоки, выполнявшие обновление электросетей и замедляющие друг друга.
Чтобы полностью забраковать эту идею, мне пришлось воспользоваться дополнительными инструментами профилирования, например, Intel VTune, которые позволили мне получить больше количественных доказательств того, что электрические сети ограничены пропускной способностью памяти. Время обновления сетей в нашем файле сохранения плейтестинга уменьшилось с 0,5 мс до 0,39 мс, однако использование CPU возросло с 0,5% до 15%. Суммарно файл сохранения не стал работать быстрее.
▍ Более хитрое обновление рабочих дронов (автор: kovarex)
В нашем офисном сохранении я заметил проблему с логистическими дронами: кто-то отключил кабель от автоматизированной системы, добавляющей новых дронов в дронстанции на Фулгоре. Спустя несколько часов мы получили перегруженные логистические сети с десятью тысячами дронов, которым некуда податься, потому что все дронстанции заполнены.
Так как никто не замечал эту проблему часами, первым делом я решил добавить алерт о нехватке мест для приземления рабочих дронов, похожий на алерт при отсутствии места для хранения.
Бездомные роботы, сбившиеся в кучи рядом с дронстанциями
Но на самом деле глубинная проблема заключалась в простоте обновления рабочих дронов. Каждый тик выполнялась итерация по всем дронам с исполнением их логики обновления. Однако чаще всего логика обновления очень проста, например, «продолжить двигаться в сторону цели», «ждать в очереди начала зарядки» и так далее.
В идее имитации сглаженного движения при более редком обновлении сущностей нет ничего нового. Мы уже использовали девять лет назад планировщик обновления чанков (FFF-161) и трюк с обновлением дыма (FFF-84), то есть применение этих техник к рабочим дронам было всего лишь делом времени.
Проблема с рабочими дронами, в отличие от дыма, заключается в том, что они выполняют множество разных задач (разные типы работ, приземление, зарядку и так далее), но у меня была мотивация к поиску ещё одного способа оптимизации игры, поэтому я приступил к делу.
Самым важным был процесс перемещения дронов, потому что основная часть логики работала просто:
- Мы уже на месте?
- Нет! Приближаемся
- Мы уже на месте?
- Да, выполняем следующую часть логики.
Но теперь нам нужно сохранять в дроне намерение двигаться и использовать его, когда нам нужно куда-то двигаться. Как только становится известно, что робот намерен двигаться дальше, он может «погрузиться в сон», а код рендеринга может использовать это намерение, чтобы притвориться, что робот постоянно движется плавно. Также это можно использовать для вычисления времени прибытия в точку, чтобы знать, на какой момент запланировать следующее обновление.
Отладочная визуализация: красный дрон (реальный) обновляется только один раз в двадцать тиков. Дрон-призрак — это прогнозируемая позиция, используемая для рендеринга
Нужно наложить некие ограничения, потому что робот способен двигаться долгое время и какие-то далёкие роботы могут двигаться в центр экрана, но мы не узнаем об этом, так как их физическое расположение будет находиться слишком далеко. Мы схожим образом проверяем края экрана (например, в случае фонарей, освещающих экран из-за его края), так что у нас есть определённая свобода манёвра. Поэтому я просто решил задать чёткие магические числа: максимум в 20 тиков для движения без обновлений и 60 тиков для приземлившегося дрона без обновлений. Вероятно, их можно увеличить, но это практически никак не повлияет на производительность.
▍ Результат
В сохранёнке офисной LAN-пати общая производительность увеличилась на 15%, в общем случае это зависит от количества дронов. Но в сохранениях с большим количеством дронов рост производительности обычно составлял 10-25%. Мне кажется, это успех.
Объединив все эти изменения, мы обеспечили более комфортную производительность для сохранений большего размера, характерных для Space Age. Но было бы здорово увеличить её ещё немного, чтобы игроки могли позволить ещё больше свободы. У нас есть другие идеи, которые могли бы сильно помочь, и надеемся, они будут историями успеха, а не постмортемами о провалах.
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
Комментарии (27)
riky
05.08.2024 16:30обновление электросети выполняет не так много действий: просто считывает две переменные, производит одно-два сложения и переходит к следующей сущности. Оно ограничено пропускной способностью памяти
кажется не используется нормально кэш процессора. кэш прогружается страницами памяти, и большую часть страницы займет эта структура "столба" с десятками свойств. надо скидывать эти "две переменные" в общий массив чтобы в кэш сразу попадало много этих "двух переменных" разных столбов за раз.
Lekret
05.08.2024 16:30+1Я думаю разрабы прекрасно знают про кеш и его особенности, люди не глупые.
Мне тоже с дивана не видно, что у них там конкретно в коде, но опираясь на кусок кода из этой статьи и ещё факт, что у них игра (или часть игры) на связанных списках, могу предположить, что при каждом изменении сети, что вероятно представлена графом, держать обновлённый линейный массив с нужными данными задача не очень простая.riky
05.08.2024 16:30думаю что не глупые, и пр кэш то уж точно знают. но бывает можно уйти в оптимизацию не туда, забыв про более очевидные и простые варианты. может быть и есть какие то еще причины, с дивана не видя код, да, сложно судить.
столбы там редко переставляются и подумать о такой оптимизации не помешало бы.факторка очень универсальная, система пользовательских модов очень сильно расширяет возможности но и закрывает возможности некоторых оптимизации. может еще поэтому не так просто оптимизировать.
mayorovp
05.08.2024 16:30Редко?! Вообще-то постоянно. Но, вообще говоря, столбы тут и ни при чём, потому что производят и потребляют энергию не столбы, а подключаемые к электросети сущности. Добавление и удаление которых буквально и является основным занятием игрока.
riky
05.08.2024 16:30даже если мы на минуту кешировали это состояние то недопотребление энергии от недавно добавленных аппаратов никто бы не заметил :)
UPS там 60. даже если вы каждые 5 сек чтото добавляете то это 300 тиков без изменений.mayorovp
05.08.2024 16:30Но для этого требуется, чтобы добавление происходило быстро, без крупных перестроений структур данных. А именно это сделать и не получится.
riky
05.08.2024 16:30получится/не получится не видя кода сложно судить. да и даже с кодом наверное там уже после всех этих оптимизаций без политры не разберешься...
aldekotan
05.08.2024 16:30+1Для тех кто не следит, у разработчиков Factorio такие истории выходят каждую неделю, по пятницам, в связи с грядущим выходом дополнения Space Age.
Highlander37
05.08.2024 16:30Нет факторка, это не заставит меня вернуться)
Последний раз в сетевой игре упирались именно в троллинг по ups. Я думаю разрабам не интересно доводить до ума для этой эпохи, всё равно кто доходит.
FreeNickname
05.08.2024 16:30Там огромное число улучшений и оптимизаций в плане UPS, можете почитать блок разработчиков "FFF", если интересно (эта статья – как раз перевод одного из постов FFF). Сильно улучшили просчёт ботов, жидкостей, электричества. Естественно, всё равно в конечном итоге при определённом размере фабрики Вы уткнётесь в UPS, но этот размер теперь будет больше.
paintdrip
05.08.2024 16:30Многопоточность для дронстанций и логика радаров — это что-то, спасибо за подробное объяснение.
Tsimur_S
05.08.2024 16:30+1Там была интересная Санта-Барбара с многопоточкой несколько лет назад, когда ее в игре вообще не было. Разрабы стояли на своем что многопоточку прикрутить нельзя, один из игроков сказал "смотрите че могу" и запилил за лето прототип факторки.
aldekotan
05.08.2024 16:30Удивляет то, что они таки потратили время на внедрение многопоточки/переняли что-то у игрока. Могли бы стоять на своём и дальше
Yaroz
Ну вот опять я захотел вернуться в Factorio!
Metaller
В октябре выходит новая версия. Как раз повод вернутся.
riky
тоже раньше любил, сотни часов, за почти 10 лет...
но постепенно начало подкрадываться чувство "вроде работаю как обычно, а пользы от этого никакой" лучше уж по настоящему чтото сделать, по сути факторка ведь такое же программирование :)
ну и заметил еще что играем мы в нее так же как и работаем, у кого то спагетти и костыли лишь бы работало и был результат, а для кого то результат - идеальная, стройная, чистая база.
net_racoon
Зато там можно сделать идеально, а не работе нельзя. Мой внутренний перфекционист счастлив.
riky
может пет проект тогда? чтобы еще и польза была, а то какие то песочные замки делаем, хоть и идеальные :)
Очень много времени там на рутину уходит. Чтобы её сокращать приодится или имбово большие залежи руды генерировать или чит на бесконечный сундук брать. иначе на большой базе только и будешь рудники переставлять да ЖД тянуть.
Те кто за архитектуру топят наверное в основном к ситиблокам приходят, типа микросервисы. Но время приходится убить чтобы это всё красиво организовать... в дрожь бросает уже, это сотни часов "работы" :)
dfgwer
После ситиблоков приходят к хайлоад и адовым оптимизациям. Максимум директ инсерта минимум ящиков, смелтинг на руде, узкоспециализированные поезда в кольцах, "темные" солнечные поля, внешнее тактирование инсертеров...
Antohin
(Ворчливо) Какое-такое спагетти? Это прототип! :)
Antohin
Мне было интересно в неё возюкаться в самом начале - ничего не понятно, НО ВСЁ ТАК ИНТЕРЕСНО. Специально не смотрел/не читал гайдов, чтобы на своем опыте до всего дойти, строил и перестраивал жуткие спагетти в рамках прототипов, костылил все это. Потом немного с чем-то разобрался, понял как надо, перезапустил заново и на каком-то этапе вдруг понял что всё превращается в рутину. Прикинул сколько еще масштабировать всё это и затосковал...
В разработке так же - кому-то нравиться исследовать, кому-то писать идеальный код. Мне и в разработке интересней разбираться с непонятной хренью,10 раз переписывать прототипы/тесты. А потом когда разобрался, сказать "народ, я понял как надо - вот так и так, если что обращайтесь, подскажу, а мне вон с той непонятной хренью надо разбираться".
w800
А мне захотелось начать)))