Как сделать майнкрафт многопоточным

Обновлено: 07.07.2024

Обработчик серверной части Майнкрафта не может работать на нескольких потоках, так как это сломает буквально всё – от работы датапаков до AI сущностей. Да и это не имеет смысла, так как многопоточная обработка вызывает больше задержек, чем однопоток.

Это наглядно доказал мод SmoothBoot, который позволяет вручную выбрать количество потоков для игры. Сам автор мода утверждает, что несмотря на то, что в моде есть такая функция, разбивка серверных вычислений на потоки зачастую лишь замедляет их, и Майнкрафт во многих случаях лучше работает на одном потоке (источник).

Тем не менее, некоторые моды-оптимизаторы всё же используют многопоточность там, где это может ускорить какой-то процесс.
Например, знаменитый Sodium активно использует мультипоточные алгоритмы для обработки визуализации чанков и их рендеринга.
А мод Tic-Tacs благодаря добавлению многопоточной генерации чанков ускоряет этот процесс раза в 2. В паре с модом Starlight производительность повышается ещё сильнее (но только в dev-версии tic-tacs 0.1.3). Правда, крайне быстрая загрузка новых чанков сильно бьёт по FPS.

Также мультипоточность используется высокопроизводительным сборщиком мусора ConcMarkSweepGC, который является лучшим решением для Майнкрафта (но по умолчанию всегда стоит G1GC).

Подытожу: Вопреки распространённому стереотипу, что многопоточность в разы ускоряет вычисления, это на самом деле далеко не так. Конечно, программисты в Mojang могли бы работать лучше, но нам это и не нужно, ведь все решения по повышению производительности уже реализованы в модах для Fabric.

Как не нужно писать большие сервера

Те, кто мог видеть мою прошлую статью (а она довольно related к данной теме), знают, что вот уже больше полутора лет я разрабатываю собственную реализацию сервера Minecraft, рассчитанную, в первую очередь, на высокие нагрузки. Тем не менее, в своей работе мы используем так же и стандартный сервер (Bukkit) для нескольких мини-серверов, просто чтобы было разнообразие. И вот, столкнувшись с очередной версией сервера, которая стала раз в 5 хуже предыдущих, я уже не выдержала, и решила написать эту статью.

Статья больше похожа на рассказ, чем на обучающий материал, так что вряд ли вы почерпнете из неё полезных навыков кодинга, но, надеюсь, кому-то она покажется интересной или даже полезной. Но если вы ходите увидеть кучу кода и примеров, то не открывайте статью, она не об этом. Об этом, надеюсь, будет следующая статья.

Вам не нужно знать ничего о майнкрафте и особенно о его сервере, в данной статье я хочу просто рассказать, как работает оригинальный сервер Minecraft, а так же его «обвязка» — Bukkit, рассказать, почему такая система не работает и не должна. Я не претендую на идеальные знания о разработке серверов и не утверждаю, что мой сервер написан правильно и лучше всех. Я просто делюсь своим опытом, основанным на двух годах работы с сервером от всем известной Mojang и на полутора годах разработки своего сервера. Вся представленная здесь информация является моим личным мнением, а статья предназначена для расширения кругозора или даже обучения и может быть интересна как новичкам, так и продвинутым профессионалам.

  • Чанки — для тех, кто не знает, весь мир Minecraft разделён на куски площадью 16х16 кубов и высотой в зависимости от настроек. Все чанки в радиусе видимости игроков загружены в память сервера и находятся в HashMap-е, каждый «тик» сервера каждый чанк обрабатывается. В это время выполняется следующее: все активные чанки (те, которые находятся в определенном радиусе от игроков) перебираются по очереди. Для каждого чанка выполняется обработка погоды (насыпать снег, ударить молнией), а так же случайная обработка блоков — из всего чанка выбирается несколько десятков случайных блоков, проверяется, нужно ли эти блоки обновлять (по типу блока) и вызывается специальная функция на выбранном блоке.
  • Тайлы — это специальные блоки, которые обрабатываются каждый тик, а не случайно. К данным блокам относятся печи (обновление статуса пережигания материала, оставшегося топлива, это должно делаться равномерно, а не случайно, как тик остальных блоков), так же там находятся спавнеры мобов (блоки, которые спавнят мобов вокруг себя), котлы для зельеварения и подобные вещи. Они все хранятся в списке (List), который заполняется при загрузке чанка или при установке нового тайла во время работы, и перебираются каждый цикл по очереди.
  • Срочные блоки — они, конечно, так не называются, но тем не менее, это блоки, которые нужно обработать «срочно», то есть на следующем цикле или с небольшой задержкой (тоже в циклах, тут все считается в циклах, даже время, даже аллах), а не случайно, т.к. случайные блоки обрабатываются раз в несколько минут в среднем. Обрабатываются примерно как тайлы, только у них может быть указана задержка, через сколько циклов их нужно обработать. Задачи на обработку обычно генерируются во время работы сервера из-за действий игрока или других блоков. В частности, так обрабатывается редстоун, который должен очень быстро реагировать на внешние изменения, блоки огня, текущая вода и подобные.
  • Обновление света — Minecraft использует статическое освещение, разделенное на блоки. Каждый блок имеет свой уровень освещённости от 15 до 0. При изменении блоков их освещённость должна быть пересчитана, алгоритм не очень сложный, но рекурсивный, а так же есть два типа освещения — от блоков (факелов, огня и тп) и от неба, они должны рассчитываться независимо, то есть два раза на каждое изменение (если в мире есть небо, в Nether его нет).
  • Entity — это практически все объекты. Мобы, игроки, предметики, валяющиеся на полу, тележки, лодки, картины, молния, стрелы и прочее. Все они хранятся в одном большом списке и по очереди на них вызывается функция tick(), перед этим проверяется, не умерли ли они, если умерли, то они удаляются из списка и из памяти сервера, соответственно.
  • Спавн мобов — тоже отдельное действие. Мобы спавнятся в определенном радиусе от игрока, при этом выбирается случайная точка в чанке и на основе нескольких сдвигов в разные стороны выбирается, можно ли поставить туда моба, и он создаётся
  • Обработка игроков — все пакеты, которые прислали игроки необходимо обработать, очевидно.
  • Загрузка и генерация чанков — если происходит попытка доступа к блоку чанка, которого нет в памяти, чанк должен быть загружен с диска, если его нет на диске, он должен быть сгенерирован. Не надо объяснять, что жесткий диск почти всегда очень узкое место. Генерация чанка ещё сложнее, чем его загрузка.
  • Сохранение чанка — во время общего сохранения сервера или просто, когда чанк долго не использовался и может быть выгружен, чанки необходимо сохранить на диск — преобразовать в поток и записать в файл.

Вы спросите, в чем же тут проблема? Так многие делают: основная логика приложения в одном потоке, это очень удобно программировать, не нужно заботиться о синхронизации и прочих проблемах параллельных приложений. Проблема тут в том, что если на сервере больше 40 человек, вместо стандартных 20 циклов он делает уже 15, если 70 человек, то 10, если 100 — то проседает до невероятных значений. Это при том, что у меня вообще-то мощный 6-ядерный Core i7 и 64Gb оперативной памяти! И куда мне теперь деть эти ресурсы, если из 12 потоков заняты от силы два?

Не буду пустословить, приведу пример:
На сервере 223 игрока, при этом радиус видимости выбран достаточно маленький, в памяти находится 46577 чанков, 524 «срочных» блока, 87 блоков редстоуна и поршней, 11240 Entity предметов, 4274 Entity животных, 19 тележек и лодок, 717 других Entity, игроки, которые тоже являются Entity и требуют соответствующей обработки.
Количество тайлов и обновлений света мой сервер не выводит в информации (мне это не нужно), но можете поверить, их много.

Одна лишь обработка животных ужасно тяжелый процесс — они регулярно выполняют поиск пути, поиск других Entity вокруг, у них есть AI (в последних версиях довольно продвинутый), поэтому обработать 4 тысячи животных — уже большая работа.

Обойти 3 миллиона блоков (примерно столько обрабатывается случайных блоков при таком количестве чанков) — тоже не тривиальная задача.

И всё это нужно успеть сделать за 50 миллисекунд, иначе все начнет тормозить, ведь скорость рассчитывается в циклах. Если сервер делает меньше циклов в секунду, чем должен, то, например, мобы начинают ходить медленно и рывками. Плюс расчётов в циклах очевиден — если сервер подвиснет или произойдёт огромная сборка мусора (сервер ведь на Java), то не получится так, что тележка, едущая на полной скорости на следующем цикле превратится в быстро движущийся маленький объект, и придётся рассчитывать её движение по более сложным алгоритмам.

При этом есть ещё и Bukkit!
Bukkit — это такой «враппер» для ванильного сервера. Он добавляет API для создания плагинов, он супер-удобен для разработчиков плагинов и сделан действительно качественно. Но, грубо говоря, всё становится только хуже. Если игрок присылает пакет, что он немного сдвинулся или повернул голову… создаётся событие и посылается по всем плагинам, которые его обрабатывают. А при этом функция обработки движения и так довольно сложная. При ломании блока или установке происходит то же самое, а так же при около сотни других действий, которые создаёт игрок или сам сервер, включая махание рукой, смена состояния редстоуна, перетекание воды, спавн моба, AI, тысячи их… То есть, как бы система хорошая, но создаёт кучу дополнительных вызовов при обработке всего.

К счастью, некоторые разработчики плагинов научились вытаскивать тяжелую логику своих плагинов в отдельный поток. Яркими и хорошими примерами служат плагины OreObfuscator и Dynmap. Первый «чистит» посылаемые игроку блоки от лишних данных, чтобы игрок не мог читами смотреть сквозь стены. Он делает это в отдельном потоке, складывая пакеты в очередь и обрабатывая их отдельно от логики сервера. Второй генерирует динамическую карту для браузера, тоже очень качественно сделан. В общем, хвала им, что не нагружают основной поток ещё сильнее.

Так же есть плагин, который уменьшает количество вещей, которые обрабатывает сервер за цикл. Объединяет лежащие рядом предметы, выгружает мобов, ограничивает обработку чанков. Это очень круто, ни один сервер не обходится без этого плагина — NoLagg.

Как делать правильно (по-моему)
Мы долго мучились со всем этим, когда полтора года назад наш онлайн вырос до 100 человек, а скорость работы просела до 0.5-1 цикла в секунду. Мы пытались делать оптимизации сервера, правили код, пытались убрать как можно больше лишнего, изменили в некоторых местах работу не по циклам а по секундам (например, в печи. В Bukkit это потом тоже добавили… через несколько месяцев). В конце концов мы достигли ужасной нестабильности сервера и решили плюнуть на всё это.

Единственным вариантом, который сможет обеспечить нам комфортный онлайн такого количества игроков, какой мы хотели, был масштабируемый сервер. Не думаю, что нужно объяснять, что поток у процесса сущность не масштабируемая, может работать только на одном ядре процессора одновременно и его производительность ограничена производительностью ядра. Ядра в процессорах сейчас довольно производительные, но и работы много, да и процессоры сейчас делают многоядерными, не то время, чтобы не делать что-то многопоточным.

Разделить уже существующий сервер на несколько потоков не представляется возможным. Многопоточное программирование — штука тонкая, сложная, требующая большого знания кода, с которым работаешь, и в существующее приложение практически не встраиваемая. Код надо писать с нуля.

Так родился сервер, в основе которого было заложено как можно больше потоков: мир делится на куски по 64х64 чанка и каждый такой кусок обрабатывает чанки в одном потоке, один поток для обработки срочных блоков, один поток для редстоуна и поршней, один поток для мобов, один поток для предметов, один поток для тележек, один поток обрабатывает другие Entity и прочую информацию о мире, один поток пересчитывает свет, четыре потока по разным частям света сохраняют мир на диск, один поток рендерит карту, один поток занимается обслуживанием сервера и команд консоли, обновляет статистику. Для игроков используется система, которая позволяет обработку пакетов ставить либо на отдельный поток для каждого игрока, либо на пул потоков, либо на отдельные потоки для каждого игрока. При этом всё можно разделить на ещё несколько потоков: обрабатывать один и тот же тип объектов хоть в 20 разных потоках. А так же Netty (NIO) в качестве сетевого движка, в отличии от стандартного I/O.

Разработка стабильной версии такого сервера, не обладающего всем функционалом, стоило примерно 8 месяцев работы одной меня без обладания опытом. Весь код рассчитан на асинхронный доступ ко всем данным. Но оно того стоило — совсем недавно мы поставили рекорд в 559 человек, которые не просто стояли в одном месте, лагали и снимались на фрапс, а проходили очень большой ивент с редстоуном, и при этом чувствовали себя комфортно.

Мораль сей басни такова: если вы рассчитываете на то, что ваш проект будет хоть сколько-нибудь популярным и думаете, что хоть чуть-чуть теоретически возможно, что на сервере будет хоть сколько-нибудь много человек… не поскупитесь на создание масштабируемой архитектуры.

Жду ваши гнилые помидоры, предложения по улучшению этой статьи, а так же предложения по тому, что бы вы хотели увидеть в следующей статье, которая когда-нибудь будет.

Поток мыслей может содержать орфографические ошибки, т.к. писалось на одном дыхании.

Полноценный процессор в Minecraft: как он работает, как на нем программировать и для чего он?

Все, наверное, знают, что в песочнице Minecraft можно делать абсолютно всё. Различные цифровые схемы и процессоры создают в Minecaft уже с давних пор. Но тот процессор, о котором я пишу, на самом деле уникален! Его название — DjCPU8.


Почему он уникален? По многим причинам. Давайте по порядку:


1) Скорость работы. Это пока самый быстрый процессор такого уровня в Minecraft. Одна инструкция выполняется примерно 1 секунду.
2) Количество памяти. Оперативной памяти аж 256 байт. Не знаю других ЭВМ с таким объемом памяти.
3) Вычислительные способности. 42 инструкции. Работает с арифметикой, логикой, стеком, вводом/выводом, регистрами и т.д.
4) Ассемблер. В других процессорах нужно вводить программу в двоичном виде. Но в DjCPU8 можно вводить ее текстом.
5) Простота. Ни с какой другой ЭВМ невозможно так просто и приятно работать.
6) Широта применения. Порты ввода/вывода можно создавать в любом месте.

Характеристики процессора DjCPU8:
1) Разрядность — 8 бит;
2) Архитектура Фон Неймана;
3) RAM 256 байт;
4) Без тактового генератора. Среднее время выполнения операции — 1 сек;
5) Стек данных — 9 байт;
6) 2 регистра общего назначения (РОН);
7) 42 инструкции;
8) Система ошибок;
9) 1 пользовательский ввод;
10) 16 портов вывода;
11) Ассемблер.

Анатомия процессора
На картинке разными цветами показаны функциональные блоки в процессоре:


— ОЗУ
— Устройство управления (УУ)
— Устройство чтения/записи ОЗУ
— устройство подачи сигнала на чтение инструкции, ее декодирование и обнуление необходимых регистров.
— вывод в порты вывода
— физически реализованный ассемблер
— стек
— система обнаружения ошибок
— различные вычислительно-преобразовательные блоки.

И еще есть несколько мелких блоков.

Как он работает

Дабы вас не утомлять, этот подзаголовок будет очень коротким.

Так как архитектура DjCPU8 фоннеймановская, то ясно, что инструкции и данные находятся в одной памяти. В момент запуска процессора сразу происходит полный сброс всех регистров и стека. Затем устройство подачи сигналов (выделен голубым цветом) подает сигнал на чтение данных из ОЗУ. Полученное число интерпретируется как инструкция. Это число подается на УУ (желтый цвет), где происходит выполнение инструкции.

Давайте возьмем для примера инструкцию load. Эта инструкция читает число из памяти и помещает его в регистр А. Итак, сначала мы прочитали из памяти число 3. Число 3 подается в УУ. УУ декодирует это число и понимает, что это инструкция load. Затем УУ начинает выполнять заданную последовательность действий. Сначала значение регистра программного счетчика увеличивается на 1. Затем читается второе число, которое интерпретируется как адрес. Допустим, адрес равен 4. Потом УУ посылает сигнал на чтение третий раз. Достается число из ячейки 4. Это число помещается в регистр А. Подается сигнал на чтение следующей инструкции. Всё, инструкция выполнена. Только представьте, за одну секунду процессор успел обратится к памяти 3 раза!

Есть инструкции сложные, и есть простые. Чем проще инструкция, тем быстрее она выполняется. Но средняя скорость примерно равна 1 Hz.

Как на нем программировать?

Вот небольшая табличка с описанием всех инструкций:

0 stop — Остановка ЦП
1 load RAM — Читает число из RAM и помещает его в А
2 loadC const — Помещает конкретное число const в А
3 store RAM — Сохраняет А в RAM
4 rand — Генерирует случайное число в A (0..255)
5 add RAM — К значению А прибавляет значение из RAM
6 sub RAM — От значения А отнимается значение из RAM
7 mult RAM — Умножает А на значение из RAM
8 div RAM — Делит А на значение из RAM
9 and RAM — Побитовая операция “И”: A и значение из RAM
10 or RAM — Побитовая операция “ИЛИ”: A и значение из RAM
11 not — Побитовая операция “НЕТ” А
12 2x — Делит А на 2
13 x/2 — Умножает А на 2
14 compare RAM — F = А — RAM
15 jump to A — Безусловный переход к ячейке, адрес которой в А
16 del RAM — Очищает ячейку в RAM
17 say — Выводит в чат значение A
18 A-R1 — Пересылка из A в R1
19 A-R2 — Пересылка из A в R2
20 R1-A — Пересылка из R1 в A
21 R2-A — Пересылка из R2 в A
22 inc — Увеличиват А на 1
23 dec — Уменьшает А на 1
24 push — Переместить число из А в стек
25 pop — Переместить число из стека в А
26 pushC const — Засунуть const в стек (А становится равный const)
27 in — ЦП на паузу, читает число из порта чтения в А
28 out — Пересылка числа из А в порт Port
29 setPort const — Установка значения порта Port в const
30 print — Посылает число в порт Port 0
31 jump const — Безусловный переход в ячейку const
32 jump if A const — Если A > 0 переход в ячейку const, иначе к следующей ячейке
33 jump if F const — Если F ≠ 0, переход в ячейку const, иначе — к следующей ячейке
34 jump If not F const — Если F = 0, переход в ячейку const, иначе — к следующей ячейке
35 storeR1 — Сохраняет значение А в памяти по адресу, которое в R1
36 storeR2 — Сохраняет значение А в памяти по адресу, которое в R2
37 loadR1 — Загружает значение по адресу R1 в А
38 loadR2 — Загружает значение по адресу R2 в А
39 incR1 — Увеличивает значение R1
40 decR1 — Уменьшает значение R1
41 incR2 — Увеличивает значение R2
42 decR2 — Уменьшает значение R2

Об инструкциях. Их есть два типа — с параметром и без. Параметр должен быть в следующей ячейке после инструкции. Например, loadC это инструкция с параметром. Следующее число 123 как раз параметр для loadC. А что делает loadC? Эта инструкция берет число и помещает его в регистр А. Итак, процессор загрузил число 123. Что дальше? А дальше он может делать с этим числом что угодно! Например, вывести число в чат. Команда say как раз так и делает.

А теперь о параметрах. Их тоже есть два типа — const и RAM. Параметр const — это просто конкретное число. Команда loadC как раз требует конкретное число. А параметр RAM уже более интересный. Это не просто число, это адрес места, откуда нужно достать число. Итак:

В отличии от предыдущей, эта программа будет работать совсем по другому! Инструкция load имеет другой тип параметра — RAM. Поэтому число 123 уже является адресом. Что же произойдет? Процессор, вместо того чтобы удовлетворится числом 123, теперь уже лезет в память и по адресу 123 достает число. Но так как мы туда ничего не записывали, то он достанет ноль. После выполнения этой программы регистр А станет равным нулю. Понятно?

Больше вы узнаете из моего руководства.
И вот еще более удобная табличка.

Для чего я создал этот процессор?
Заметил, что многие задают этот вопрос. И даже дополняют — «мог бы лучше деньги зарабатывать».

Ответ прост — это приносит мне удовольствие. Кто-то развлекается тем, что ПвПшит в Доту, кто-то рисует, кто-то программирует, а я делаю процессор.

И плюс к этому, во время создания своего процессора я понял, как же на самом деле работает настоящий процессор.

Как запустить майнкрафт 1.12.2 не на одном ядре, а в многопотоке?

У меня Ryzen 1600x, у меня 12 потоков, и я вижу что нагрузка ложится только на 1 ядро, а остальные простаивают, с модами на IC2 и галактик крафт, очень часто начинаются фризы, будто не хватает чего то. Хотя с оперативкой проблем не наблюдаю. Вижу что нагрузка на цпу в майнкрафте в 100% постоянно, хотя в диспетчере при мониторинге я вижу что он выше 20% не нагружается. В гугле нашел инфу только для линукса, а про клиент на винде очень мало. а переезжать на линукс для игр не готов, на линуксе я только работаю, с играми там все очень туго.

Ты явно делаешь что то не так, а может маин настолько древний что в однопотоке работает

у меня лицензия, это во первых, во вторых майн не древний, версия майнкрафта 1.12.2 — обновление Minecraft: Java Edition, которое вышло 18 сентября 2017 года.
Не играю на самом новом т. к. там еще нету модов.

Андрееей Борикиен Гуру (4567) Jenya Sydorenko, ну хз, я в маин не играю, не скажу, но попробуй все же поставить новую версию, ради интереса

Запусти игру, зайди в процессы, найди Майнкрафт, нажми на нем ПКМ/Задать Соответствие и посмотри на всех ли ядрах стоят
галочки
И через что смотришь во время игры ?! Рекомендую MSI Afterburner, если нету

Читайте также: