Идея CPU Affinity

Reality_SC

Старожил
Пользователь
Баллы
123
Имя в Minecraft
Reality_SC
Все в курсе, что нынче все компы имеют по несколько параллельно работающих ядер.
Чаще всего половина ядер это виртуальные процессоры (Intel Hyper-Threading), которые являются фронт-эндом к совместным логическим устройствам ядра.
Примем в дальнейшем, что у нас всегда процессор с HT.

Если HT отключить, 1 поток на рассматриваемом ядре будет идти со своей обычной скоростью 100%.

Если HT включить, ядро сможет обрабатывать параллельно 2 потока, но при этом их суммарная производительность не будет выше 125% (личный опыт), Википедия пишет, что 115-130%. При этом отдельная задача на одном виртуальном процессоре работает медленнее (125%/2 = 62.5%). Это относится к случаю, когда на втором CPU этого же ядра работает ресурсоёмкая задача. Если её нет, то в общем-то грамотная микросхема переносит все силы на тот первый поток, который работает, и можно считать, что HT не работает для одного выделенного ядра.
Конечно, в целом HT положительно влияет на работу многозадачной системы с кучей потоков.

Серверу Minecraft всегда требовалась максимальная производительность, по крайней мере его основному циклу. С другой стороны, нельзя сказать, что сервера совсем однопоточные — сегодня лишь самый ленивый автор плагинов не использует параллельные потоки для того, чтобы выполнять какие-то вычисления.

Отсюда встаёт задача: как эффективно распределить потоки по ядрам?

Интуитивно, сразу напрашивается такая схема (на примере 2-ядерного/4-поточного CPU):
  1. ЯДРО:
    1. Для нужд ОС, других программ и параллельных потоков mc-сервера.
    2. Для нужд ОС, других программ и параллельных потоков mc-сервера.
  2. ЯДРО:
    1. Только для главного цикла mc-сервера.
    2. Никому.
Предполагаю, что в такой ситуации сервер станет способен обрабатывать немного больше эвентов при тех же своих 20 TPS.

Для достаточно частого случая 4-ядерного CPU (8 потоков) предлагаю также отдать ядро под серверный цикл, а остальные 3 ядра поделить по 3 потока на ОС и серверные параллельные потоки.

Ко всему RuBukkit-у: имеют ли мои размышления достаточное основание, чтобы написать такой плагин? Или это заблуждение, и планировщик процессов уже всё сделал до меня?
@HoShiMin @alexandrage @Shevchik @fromgate кто ещё? 🙂
@MySt1k
Может быть это уже кто-то делал у себя, тестировал, и может поделиться результатом?

Предлагается накидать плагин, а в конфиге указывать две маски cpu affinity: для основного потока и для остальных потоков текущего процесса Java. OpenHFT в помощь, возможно есть другие библиотеки.
Предположить, что остальные процессы займут остальные ядра. Можно, конечно, заморочиться, и пройтись по всем остальным процессам и выставить им третью маску, но я хз как это сделать под Linux, под Windows вроде как легко, достаточно иметь админские права.
 
Последнее редактирование:
Ты забыл про GC и про то что Netty использует свой пул потоков. В итоге потоков больше чем ядер полюбому. Так что смысл есть. С другой стороны есть куча потоков которым нужны ядра, и их ограничивать плохо.
Так что ожно попробовать, но может так оказаться что просядет сеть и будет высокий пинг у всех, либо просядет GC и JVM будет встревать надолго пытаясь вычистить память. Это всё надо проверять на практике для многих разных конфигураций.
Но в принципе при 4 реальных ядах на 1 сервер пруфит должен быть. (При условии использования G1GC, либо хорошо затвиканного CMSGC).
Короче пойду чейчас набросаю, а дальше посмотрим как оно получится.
UPD: Библиотеку пришлось сильно порезать из-за ограничений накладываемых на плагины баккитом, но работать будет,

P.S. Раз уж заговорили о системных твиках, то стоит упомянуть что в Linux есть ещё и файловый кэш и имеет смысл загонять туда все файлы из папки plugins, а также world/playerdata. Вот это реально помогает.
 
Последнее редактирование:
Для теста по-хорошему нужен сервер, где из-за нагруженности tps меньше 20, чтобы можно было вычислить профит. Я не представляю пока, как замерить прирост скорости, если tps и до и после равен 20 🙂
ы забыл про GC и про то что Netty использует свой пул потоков. В итоге потоков больше чем ядер полюбому. Так что смысл есть. С другой стороны есть куча потоков которым нужны ядра, и их ограничивать плохо.
Так что ожно попробовать, но может так оказаться что просядет сеть и будет высокий пинг у всех, либо просядет GC и JVM будет встревать надолго пытаясь вычистить память. Это всё надо проверять на практике для многих разных конфигураций.
Как вариант для четырёх ядер:
CPU 0/1/2/3/4/5 — всем потокам кроме главного, маска 00111111b, 0x37.
CPU 6 — только главному циклу, маска 01000000b, 0x40.
CPU 7 — никому.
При допущении, что на машине нет MySQL или чего-то такого объёмного, а только сервер, должен быть профит.
Java:
for(Thread thread : Thread.getAllStackTraces().keySet()) /* выставить всем 0x37 */
...
/* выставить Thread.currentThread 0x40 */
 
А если у сервера > 20 тпс, то значит у главного потока есть время спать, что значит мы отбираем доступную мощность и других потоков.
 
Главное, чтобы остальным хватало ядер, что при суммарных 4 (реальных) вполне правда.
 
У OpenHFT немного другой апи позволяющий локнуть тред к ядру. Так что достаточно вызвать 1 функцию.

Но при меньше чем 4х ядерном процессоре все это не имеет смысла. На 2х ядрах ни один из сборщиков мусора не вытягивает сервер без огромных задержек на остановку jvm. Впрочем у двухядерников начиная с 1.8 все итак плохо.
 
Последнее редактирование:
Насчёт опытного образца, ты попробуешь?
 
Уже запилил сейчас тестирую. Пришлось поменять немного вещйе в библиотеке правда, но в общем работает (1 ядро остаётся не нагруженным пока сервер работает что бы я не делал снаружи). На локальном компе естественно разницы нет ибо 1 игрок нагрузки никакой не даёт. Осталось только как-то замерить пруфит от этого, поставлю ближе к вечеру на сервер и посмотрим как оно себя поведёт.

P.S. HT - полная шняга, потому что когда нагрузка падаёт на одно виртуальное ядро, то оно не выдаёт столько же как оно же, но без HT, хрен его знает почему. Походу при 4х и больше реальных ядрах HT только мешает.
 
Последнее редактирование:
Уже запилил сейчас тестирую. Пришлось поменять немного вещйе в библиотеке правда, но в общем работает (1 ядро остаётся не нагруженным пока сервер работает что бы я не делал снаружи). На локальном компе естественно разницы нет ибо 1 игрок нагрузки никакой не даёт. Осталось только как-то замерить пруфит от этого, поставлю ближе к вечеру на сервер и посмотрим как оно себя поведёт.

P.S. HT - полная шняга, потому что когда нагрузка падаёт на одно виртуальное ядро, то оно не выдаёт столько же как оно же, но без HT, хрен его знает почему. Походу при 4х и больше реальных ядрах HT только мешает.
Надо использовать по 2 потока на 1 задачу, тогда и есть прирост.(~20%)
 
Надо использовать по 2 потока на 1 задачу, тогда и есть прирост.(~20%)
Это понятно, я говорю про то что со включённым HT, одно из логических ядер не жмёт столько же как если бы HT был отключён (Логическое ядро выброшено из шедулера чтобы оно точно не использовалось). Возможно дело в TurboBoost, надо будет замерить как TurboBoost работает с HyperThreading.
Кстати над TurboBoost тоже покурить, у кого ядер много, тому точно будет выгоднее одно вырубить, чтобы другое к которому привязан главный поток работало быстрее.
 
Это понятно, я говорю про то что со включённым HT, одно из логических ядер не жмёт столько же как если бы HT был отключён (Логическое ядро выброшено из шедулера чтобы оно точно не использовалось). Возможно дело в TurboBoost, надо будет замерить как TurboBoost работает с HyperThreading.
Кстати над TurboBoost тоже покурить, у кого ядер много, тому точно будет выгоднее одно вырубить, чтобы другое к которому привязан главный поток работало быстрее.
Зачем так окостыляться? Просто подождём, когда jeb перепишет сервер(если перепишет).
 
Зачем так окостыляться? Просто подождём, когда jeb перепишет сервер(если перепишет).
Быстрее я с нуля напишу сервер, чем джеб напишет годный сервер.
Отсюда встаёт задача: как эффективно распределить потоки по ядрам?
Не нужно. Ощутимого профита не выйдет в любом случае.
 
Основная проблема то что надо добавлять параметры в ядро чтобы можно было заизолировать CPU (чтобы его не пытались использовать другие процессы), а это не очень удобно хотелось бы всё-таки чтобы плагин можно было поставить и забыть.
Можно конечно поллить все процессы в системе, и выставлять им affinity, но это глупо, но придётся так делать. Но тут ещё одна проблема, без рута можно выставить affinity только процессам которые были запущены пользователем.
Ну вот короче написал, работает только на Linux 64bit.
Сам по себе при запуске в качестве плагина локает главный поток сервера на указанное в конфиге ядро, но другие потоки всё ещё могут использовать это ядро. Для того чтобы этого избежать перед запуском сервер надо просто запустить этот джарник, как самый обычный джарник, но от рута. В качестве аргументов надо передать номер ядра которое вы хотите отобрать у всех потоков, при этом все остальные ядра буду возвращены. Если аргументы не указать то все ядра снова станут доступны всем потокам. Пример:
Код:
sudo java -jar MainThreadAffinity.jar 5
- отбирает ядро номер 5 у всех потоков.

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

https://dl.dropboxusercontent.com/u/71299154/MainThreadAffinity.jar
 
Последнее редактирование:
Основная проблема то что надо добавлять параметры в ядро чтобы можно было заизолировать CPU (чтобы его не пытались использовать другие процессы), а это не очень удобно хотелось бы всё-таки чтобы плагин можно было поставить и забыть.
Можно конечно поллить все процессы в системе, и выставлять им affinity, но это глупо, но придётся так делать. Но тут ещё одна проблема, без рута можно выставить affinity только процессам которые были запущены пользователем.
Ну вот короче написал, работает только на Linux 64bit.
Сам по себе при запуске в качестве плагина локает главный поток сервера на указанное в конфиге ядро, но другие потоки всё ещё могут использовать это ядро. Для того чтобы этого избежать перед запуском сервер надо просто запустить этот джарник, как самый обычный джарник, но от рута. В качестве аргументов надо передать номер ядра которое вы хотите отобрать у всех потоков, при этом все остальные ядра буду возвращены. Если аргументы не указать то все ядра снова станут доступны всем потокам. Пример:
Код:
sudo java -jar MainThreadAffinity.jar 5
- отбирает ядро номер 5 у всех потоков.

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

https://dl.dropboxusercontent.com/u/71299154/MainThreadAffinity.jar
В воскресенье протестим. 50+ плагинов. 70+ онлайн.
Токо я не понял. Это плагин или приложение?
Или то и то?
Тоесть я должен закинуть это в плагины и создать отдельный скрин для java -jar ..... ... 5?
 
В воскресенье протестим. 50+ плагинов. 70+ онлайн.
Токо я не понял. Это плагин или приложение?
Или то и то?
Тоесть я должен закинуть это в плагины и создать отдельный скрин для java -jar ..... ... 5?
И то и то. Должен и запихнуть как плагин, и запустить ка приложение, но как приложение его держать не обязательно, достаточно просто один раз запустить с нужными параметрами.
Обновил джарник, чуть-чуть переделал внтуреннюю структуру чтобы было легче добавлять поддержку других OS. (Хотя лично я добавлять не буду, но другие смогут когда выложу на гитхаб).
 
Как повезёт, маппится ядра с HT могут по разному, так что лучше его отключить. Насчёт остальных потоков - подумаю что можно сделать. Сделаю чтобы в конфиге можно было памить главный поток, дефолт для всех остальных, и возможность настроить по имени потока.
Нет, всё-таки не будет такой возможности, слишком много гемора в вытаскивании нативных айдишников потоков из жаба потоков так что только так пока-что.
 
bfa36bf70d0b89643c2e31c381090dce.png

И какое ядро блокировать?
И будет ли робить на Debian 8 x64?
 
Если у тебя всего один сервер на машине, то не имеет смысла. Бороться за ресурсы ему не с кем.
Суть то в том чтобы отдать целое ядро под основной цикл. А другие на растерзание потоками из других плагинов.
 
1-3, выбирай любой.
 
Последнее редактирование:
Назад
Верх