Boost Asio с одной резьбой

Я использую настраиваемый сервер, который должен поддерживать очень большое число (100 тыс. или более) долгоживущих соединений. Сервер просто передает сообщения между сокетами и не выполняет никакой серьезной обработки данных. Сообщения небольшие, но многие из них принимаются/отправляются каждую секунду. Одной из целей является сокращение латентности. Я понимаю, что использование нескольких ядер не улучшит производительность, поэтому я решил запустить сервер в одном потоке, вызвав методы run_one или poll объекта io_service. В любом случае многопоточный сервер будет намного сложнее реализовать.

Каковы возможные узкие места? Syscalls, пропускная способность, завершение очереди/демультиплексирование событий? Я подозреваю, что диспетчерам диспетчеризации может потребоваться блокировка (что делается внутри библиотеки asio). Можно ли отключить даже блокировку очереди (или любую другую блокировку) в boost.asio?

EDIT: связанный вопрос. Улучшена ли производительность системы syscall с несколькими потоками? Я чувствую, что из-за того, что syscalls атомарно/синхронизировано ядром, добавление большего количества потоков не улучшит скорость.

Ответ 1

Возможно, вы захотите прочитать мой вопрос несколько лет назад, я спросил его, когда сначала исследую масштабируемость Boost.Asio при разработке системного программного обеспечения для Суперкомпьютер Blue Gene/Q.

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

После того, как вы внедрили свое приложение с использованием одного потока и одного io_service, я предлагаю исследовать пул потоков, вызывающих io_service::run(), и только потом изучить привязку io_service к определенному потоку и/или процессору. Есть несколько примеров, включенных в документацию по Asio для всех трех этих проектов, и несколько вопросов на SO с дополнительной информацией. Имейте в виду, что при вводе нескольких потоков, вызывающих io_service::run(), вам может потребоваться реализовать strand, чтобы гарантировать, что обработчики имеют эксклюзивный доступ к общим структурам данных.

Ответ 2

Используя boost:: asio, вы можете писать однопоточный или многопоточный сервер примерно с одинаковой стоимостью разработки. Вы можете написать однопоточную версию в качестве первой версии, а затем преобразовать ее в многопоточную, если необходимо.

Как правило, только узким местом для boost:: asio является то, что реактор epoll/kqueue работает в мьютексе. Таким образом, только один поток выполняет epoll одновременно. Это может снизить производительность в случае, если у вас многопоточный сервер, который обслуживает партии и много очень маленьких пакетов. Но, imo это в любом случае должно быть быстрее, чем простой однопоточный сервер.

Теперь о вашей задаче. Если вы хотите просто передавать сообщения между соединениями - я думаю, это должен быть многопоточный сервер. Проблема заключается в syscalls (recv/send и т.д.). Команда очень легко думать о процессоре, но любой syscall не очень "легкий" (все относительно, но относительно других заданий в вашей задаче). Итак, с одним потоком вы получите большие системные расходы накладные расходы, поэтому я рекомендую использовать многопоточную схему.

Кроме того, вы можете отделить io_service и заставить его работать как идиома "io_service per thread". Я думаю, что это должно дать лучшую производительность, но у него есть недостаток: если один из io_service получит слишком большую очередь - другие потоки не помогут, поэтому некоторые подключения могут замедлить работу. С другой стороны, с одним io_service - переполнение очереди может привести к большим накладным расходам. Все, что вы можете сделать - сделать оба варианта и измерить полосу пропускания/задержки. Это не должно быть сложным для реализации обоих вариантов.