Дневник разработчиков Stellaris №181 — Потоки и время загрузки
Всем привет, The French Paradox на связи!
Вся команда разработчиков Stellaris надеется, что вы хорошо провели летние каникулы, учитывая нынешнюю обстановку в мире и всё такое!
Мы все вернулись к работе, хоть пока и не в офисе. Впереди нас ждёт очень вдохновляющая осень и зима со множеством интересных новостей! И нас нетерпится поделиться этими новостями с сообществом в ближайшие недели и месяцы!
Сегодня я приоткрою завесу тайны над предстоящим обновлением 2.8 и расскажу о технической составляющей, над которой наши программисты трудились всё лето. Остальные члены команды расскажут о новом контенте и особенностях в будущих дневниках.
Но довольно вступлений — поговорим о потоках!
Потоки? Что за потоки?
Есть одна шутка о том, что игроки постоянно спорят, что выйдет раньше: Виктория 3 или игра от PDS, которая будет использовать больше одного потока.
Я знаю, что именно так вы и представляете наши самые важные совещания
Боюсь, мне придётся развеять этот миф (опять): все нынешние игры PDS используют потоки, от EU4 до CK3. Даже Stellaris! Чтобы лучше понять суть шутки и откуда она появилась, нам придётся окунуться в историю. Мне тут сказали, что вы любите историю.
Долгое время индустрия программного обеспечения полагалась на «Закон Мура», согласно которому процессор, который создадут через 2 года, будет примерно вдвое мощнее нынешнего.
Это отлично работало в 90-е, когда частота процессоров возросла с 50 МГц до 1 ГГц за десять лет. Так продолжалось до 2005, пока мы не подобрались к отметке в 3.8 ГГц. И после этого она расти перестала. С тех прошло 15 лет, а частота процессоров осталась примерно на том же уровне.
Оказалось, что из-за законов физики становится не слишком-то эффективно повышать скорости выше 3-4 ГГц. Поэтому инженеры пошли по другому пути и начали "делить" процессоры на ядра и процессорные потоки. Поэтому сегодня при выборе процессора вы обращаете большее внимание на число ядер, а не на их частоту. Закон Мура всё ещё действует, но, говоря на языке стратегий, индустрия процессоров дошла до софт капа в попытках играть ввысь, поэтому сменила мету и начала играть вширь.
Этот сдвиг вызвал глубокие изменения в индустрии программного обеспечения, потому что писать код, который будет быстрее работать с более быстрым процессором легко — почти любой код будет вести себя именно так. Но вот задействовать потоки и ядра уже куда сложнее. Программы не умеют как по волшебству "делить" работу на 2, 4 или 8, чтобы нагружать несколько ядер одновременно. Такую возможность в программу должны заложить её создатели.
Потоки ничуть не быстрее
Что приводит нас обратно к нашим играм и полным сомнений комментариев на форуме: «А игра использует потоки?». Ответ — да, конечно! На самом деле, мы используем их так активно, что пару релизов назад столкнулись с ошибкой, что игра просто не запускалась на процессорах, у которых 2 или меньше ядер.
Но, думаю, настоящий вопрос звучит так: «Эффективно ли вы задействуете потоки?». И ответ будет: «Когда как». Как я уже говорил раньше, эффективно использовать много ядер — это намного сложнее, чем использовать больше тактов. В нашем случае есть две главных задачи, которые нужно решить, распределяя работу по потокам: последовательность и упорядочивание.
Ошибки последовательности возникают, когда двум одновременно выполняющимся вычислениям требуется доступ к одним и тем же данным. Например, давайте представим, что мы вычисляем производство двух поселений: Прикки-ти и Блорга. Они оба обращаются к текущему запасу энергии, добавляют к нему своё производство энергии и записывают значение в блок данных. В зависимости от последовательности, они оба могут считать первоначальное значение (скажем, 100), добавить к нему своё производство (скажем, 12 и 3 — у Блорга просто день не задался) и записать его обратно. В идеале на выходе мы хотим получить 115 (100+12+3). Но вполне вероятно, что они оба возьмут первоначальные 100, посчитают и перезапишут итог друг друга, и в конце мы получим либо 112, либо 103.
Простой способ избежать этого — добавить блокировки. Прикки-ти "блокирует" значение энергии, пока не закончит с подсчётами и не запишет новое значение в блок данных, после чего Блорг сможет взять уже его и добавить к нему своё значение. Но решая одну проблему, такой подход создаёт новую и ещё более неприятную: действия снова стали последовательными, и то, что мы совершаем их в параллельных потоках, не имеет никакого смысла. Кроме того, приходится тратить время на блокирование, разблокирование и синхронизацию значений, так что всё это будет работать даже медленней, чем если бы мы просто посчитали всё по очереди в одном потоке.
Вторая задача — упорядочивание, или «зависимость от порядка». Это значит, что в некоторых случаях от перестановки операций меняется исход. Например, наши Прикки-ти и Блорг хотят разрешить свой спор в присущей им мирной манере. Мы знаем, что боевая система обработает всех участников, но пределах одного сражения могут быть сотни разных действий, и мы не знаем, какое произойдёт раньше. И очень вероятно, что на двух разных компьютерах порядок будет отличаться. То есть, на севере первое действие совершит Прикки-ти, а у клиента первым будет Блорг.
#БлоргВыстрелилПервым
На сервере первым обрабатывается действие Прикки-ти, после чего Блорг погибает. Последующие действия Блорга отклоняются (вероятно, в другом потоке), потому что мёртвые не дамажут (это научный факт). Но клиент пришлёт серверу совсем иной результат (может у него больше ядер, чем у сервера), и в его мире Блорг шлёпнул Прикки-ти раньше, и тот уже не смог ответить. И вот, оба игрока получают ужасающее «Ошибка синхронизации» и их реальности разделяются.
Конечно, эту проблему тоже можно решить, но тогда придётся всё переделывать так, чтобы удовлетворить обоим этим ограничениям. Например, в первом случае каждый поток может хранить у себя количество произведённых ресурсов для каждой империи, и в конце всё складывать. И с нашими двумя дуэлянтами всё можно решить, записывая урон сразу, но применяя его позже, чтобы исключить неопределённость порядка действий.
Думаю, вы понимаете, что куда проще сразу разрабатывать систему с оглядкой на многопоточность, чем подстраивать под неё старую систему. Если не верите, то просто обратите внимание, сколько времени вы тратите на переделывание своих флотилий, я подожду.
Хорошие новости
Это всё здорово, но как же это отразится на вас в следующем патче? Думаю, вы будете рады узнать, что я потратил немало времени и применил это всё к одной из самых старых частей игры: системе загрузки файлов и ассетов.
Мы очень долго использовали для этого стороннее ПО. И хотя это позволило нам не решать часть проблем, ПО, как выяснилось, плохо справляется с разделением на потоки. С большим числом ядер иногда всё работало даже медленнее. Это было особенно заметно на примере проблем с блокировкой, о которых я рассказал выше.
В сочетании с рядом оптимизаций, нас удалось значительно сократить время запуска игры.
Я сравнивал на своём домашнем ПК с древним i7 2600K и SSD. Оба запуска были «горячими» (игра недавно запускалась), но в ходе экспериментов я выяснил, что даже на «холодных» запусках разница значительна.
Для наибольшей скорости вам понадобится использовать новую бета версию движка рендеринга на DirectX11. Да, глаза вас не обманывают: новый патч будет сопровождаться открытой бетой, заменяющей старый рендерерс DX9 на новый с DX11, который изначально сделали наши друзья в Tantalus для консольной версии Stellaris. Несмотря на то, что внешне они неразличимы, использование DX11 для отрисовки графики открывает дорогу для целого спектра многопоточной оптимизации, которую сложно или невозможно получить, используя DX9. Со старым движком вы всё равно получите некоторый прирост скорости — этап с загрузочным экраном должен занимать куда меньше времени, но вы вряд ли увидите «прыжок» полоски загрузки, как на DX11, когда игра загружает модели и текстуры.
Кое-что из этой оптимизации используется и на новых версиях Clausewitz и будет в релизной версии CK3. Imperator тоже должен выиграть от этих улучшений. Возможно, нам удастся расширить их на EU4 и HoI4, но пока что мои эксперименты с EU4 не привели к такому сильному приросту скорости, как в Stellaris и CK3.
Если хотите узнать больше технических деталей об оптимизации, призванной ускорить работу Stellaris, можете ознакомиться со статьёй, которую я недавно опубликовал у себя в блоге.
И на этом я вас покину. Скорее всего, это мой последний дневник по Stellaris, поскольку в следующем месяце я перехожу в команду программистов HoI4. Считайте эту оптимизацию моим прощальным подарком.
Пусть я недолго работал над Stellaris, но после моего ухода у вас ещё останется Jeff!
Для просмотра ссылки Зарегистрируйтесь