2016-04-20 1 views
5

Я - разработчик бэкэнда на C++. Я разрабатываю серверную часть для игры в реальном времени. Итак, архитектура приложения выглядит так:Как выбрать правильное количество потоков для многопоточного приложения C++?

1) У меня есть класс Client, который обрабатывает запросы от игрового клиента. Примеры запросов: войдите в систему, купите что-нибудь в магазине (внутренний магазин игры) или сделайте кое-что. Также этот клиент обрабатывает пользовательские события ввода от игрового клиента (часто это события, которые отправляются десять раз подряд с игрового клиента на сервер, когда игрок играет в геймплей).

2) У меня есть пул потоков. Когда игровой клиент подключается к серверу, я создаю экземпляр клиента и привязываю его к одному из потоков из пула. Итак, у нас есть отношения друг с другом: одна тема - много клиентов. Round-robin используется для выбора нити для привязки.

3) Я использую Libev для управления всеми событиями внутри сервера. Это означает, что когда экземпляр клиента получает некоторые данные от игрового клиента через сеть или обрабатывает какой-либо запрос или пытается отправить некоторые данные через сеть игровому клиенту, он блокирует поток hi. Пока он делает некоторые вещи, другие Клиенты, которые разделяют один и тот же поток, будут заблокированы.

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

Теперь приложение работает на сервере с 24 логическими процессорами (cat /proc/cpuinfo). И я установил размер пула потоков до 24 (1 процессор - 1 поток). Это означает, что с текущими онлайн-участниками 2000 каждый поток обслуживает около 84 экземпляров клиента. top говорят, что процессоры использовали менее 10 процентов.

Теперь вопрос. Если я увеличиваю количество потоков в пуле потоков, это увеличивает или уменьшает производительность сервера (накладные расходы на переключение контекста и заблокированные клиенты в потоке)?

UPD 1) Сервер имеет асинхронной IO (libev + Epoll), поэтому, когда я сказал, что клиент заблокирован, когда отправлять и получать данные, которые я имею в виду, справляясь в буфер. 2) Сервер также имеет фоновые потоки для медленных задач: операции с базами данных, жесткие расчетные операции, ...

+1

Как правило: количество бегущих потоков должно быть примерно равно числу ядер ЦП. –

+1

Хорошо, но как насчет ситуации, когда 10 потоков простаивают и ждут некоторых событий и 14 потоков перегружены. Я думаю, что если у сервера всего 10% нагрузки, я могу увеличить количество потоков, потому что некоторые из них спят. –

+2

«пул потоков является узким местом для применения» - это смелое утверждение, что заставляет вас так думать? Во всяком случае, вы спрашиваете, что такое производительность вашей системы. Первое, что вам нужно сделать, это настроить способы измерения. Во-первых, ваш вопрос становится устаревшим. Во-вторых, вы можете измерить свои попытки улучшить производительность. –

ответ

1

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

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

Это дает дополнительное преимущество, что вы можете использовать тот же стресс-тест наряду с профилировщиком, чтобы определить, можно ли извлечь какую-либо производительность из своей реализации.

1

Оптимальное количество потоков чаще всего равно либо количеству ядер в вашей машине, либо вдвое больше ядер.Чтобы обеспечить максимальную пропускную способность, должны существовать минимальные точки раздора между потоками. Это число, т. Е. Количество конкурирующих точек плавает между значением количества ядер и удвоенным числом ядер.

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

1

С идеей иметь один поток на ядро ​​может быть приятным.

Кроме того, в некоторых случаях вычисление WCET (наихудшего времени выполнения случая) - это способ определить, какая конфигурация является более быстрой (ядра не всегда имеют одинаковую частоту). Вы можете легко измерить его с помощью таймеров (от начала функции до конца и вычесть значения для получения результата в миллисекундах).

В моем случае я также должен был работать над потреблением, так как это было встроенная система. Некоторые инструменты позволяют измерить потребление ЦП и, следовательно, решить, какая конфигурация наиболее интересна в этом конкретном случае.

1

Оптимальное количество потоков зависит от того, как ваши клиенты используют процессор.

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

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

В вашем сценарии я бы подумал, что это второй случай. Потоки заблокированы, потому что 24 клиентских события активны, но используют только 10% процессора (так что события, обработанные потоком, теряют 90% его ресурса cpus). Если это так, было бы неплохо поднять количество потоков до примерно 240 (количество ядер * 100/средняя загрузка), чтобы другой поток мог работать на процессоре холостого хода.

Но будьте осторожны: если клиенты связаны с одним потоком (поток обрабатывает клиентов 1, 2, 3 и поток B обрабатывает клиентов 4, 5, 6), то помогает увеличить threadpool, но могут быть спорадические задержки если два клиентских события должны обрабатываться одним и тем же потоком.

2

Ну мало вопросов.

2) У меня есть пул потоков. Когда игровой клиент подключается к серверу, я создаю экземпляр клиента и привязываю его к одному из потоков из пула. Итак, у нас есть отношения : один поток - много клиентов. Round-robin использовал , чтобы выбрать нить для переплета.

Вы не указали асинхронный ввод-вывод в любой из точек, я считаю, что ваше истинное узкое место здесь не является количеством потоков, а тем фактом, что поток заблокирован из-за действия ввода-вывода. используя асинхронный IO (который составляет не действие IO в другом потоке) - скорость вашего сервера увеличивается огромными магнитами.

3) Я использую Libev для управления всеми событиями внутри сервера. Это означает, что экземпляр клиента получает некоторые данные от игрового клиента через сеть, или обрабатывает некоторый запрос, или пытается отправить некоторые данные через сеть на игровой клиент, который он блокирует поток hi. Пока он делает что-то другое Клиенты, которые используют один и тот же поток, будут заблокированы.

еще раз, без асинхронного ввода-вывода этой архитектуры очень много 90-х серверной архитектуры (а-ля Apache). для максимальной производительности ваши потоки должны выполнять только связанные с ЦП задачи и не должны ждать каких-либо действий ввода-вывода.

Таким образом, пул потоков является узким местом для применения. Увеличить номер одновременных игроков на сервере, которые будут играть без задержек Мне нужно увеличить количество потоков в пуле потоков.

Мертвые неправильно. читайте о проблеме параллелизма 10k.

Теперь вопрос. Если я увеличиваю количество потоков в пуле потоков, то это увеличивает или уменьшает производительность сервера (переключение на переключение контекста против заблокировано Клиенты на поток)?

Итак, анекдот о количестве потоков, как количество ядер действуют только тогда, когда ваши темы делают только центральных процессор связана задачи, и они никогда не блокируются, и они ая 100% staurated с задачами центрального процессора. если ваши потоки также блокируются блокировками или действиями IO, этот факт прерывается.

Если мы посмотрим на стороне архитектуры общего сервера, мы можем определить, что лучший дизайн нам нужно

Apache архитектуры стиля:
имеющий фиксированного размера пула потоков. посылая поток каждому соединению в очереди соединений. неасинхронный IO.
Достоинства: не.
минусы: экстремально плохой пропускной

NGNix/Node.js архитектура:
, имеющие моно нарезкой - мультифункциональный обработке приложений. используя асинхронный IO.
pros: простая архитектура, которая устраняет многопоточные проблемы. отлично подходит для серверов, обслуживающих статические данные.
Против: Если процессы требуют данных shrare, огромное количество процессорного времени сжигается при просеивании и передаче данных между процессами. Кроме того, многопоточное приложение может повысить производительность при правильном выполнении.

Современные .Net архитектура:
, имеющий многопоточных-моно обработанного приложения. используя асинхронный IO.
профи: если все сделано правильно, производительность может взорваться!
минусы: Это несколько сложно настроить многопоточное приложение и использовать его без искажения данных на экране.

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

Если вы используете Linux, Proxygen Facebook может управлять всем, о чем мы говорили (многопоточный код с асинхронным IO), для вас. эй, facebook используют его!

+0

Да. Ваше право на асинхронный ввод-вывод. Я пишу об этом в комментариях ниже. У меня есть дополнительный пул потоков (называйте их фоновыми потоками), который обслуживает медленные задачи, а не блокирует потоки клиента. Итак, если нам нужно сделать что-то малое - я обрабатываю его в пуле потоков клиента. –

+0

Это также не очень хороший способ управлять ресурсами ОС. например, размер стека по умолчанию для linux равен 8 МБ. наличие 100 потоков для операций ввода-вывода только означает: 1. вы можете выполнить 100 IO одновременных действий максимум. 2. Вы используете 800 МБ для НИЧЕГО. переключиться на асинхронный ввод-вывод. –

+0

Ок. Я понимаю. Проблема с устаревшим кодом. Базовая архитектура была передо мной. Я стараюсь сделать это лучше. –