Как избежать того, чтобы реактор Boost ASIO ограничивался одним ядром? - PullRequest
7 голосов
/ 13 августа 2011

TL; DR: Возможно ли, что я ограничил производительность реактора? Как бы я сказал? Насколько дорого и масштабируемо (между потоками) внедрение io_service?

У меня есть массивно параллельное приложение, работающее на машине HyperTreaded-Dual-Quad-Core-Xeon с тоннами оперативной памяти и быстрым SSD RAID. Это разработано с использованием boost :: asio.

Это приложение принимает подключения примерно от 1000 других компьютеров, считывает данные, декодирует простой протокол и перетасовывает данные в файлы, отображаемые с помощью mmap (). Приложение также предварительно извлекает «будущие» страницы mmap с помощью madvise (WILLNEED), поэтому вряд ли оно будет блокировать ошибки страницы, но, чтобы быть уверенным, я попытался создать до 300 потоков.

Это работает на ядре Linux 2.6.32-27-generic (Ubuntu Server x64 LTS 10.04). Версия Gcc - 4.4.3, версия boost :: asio - 1.40 (обе являются Ubuntu LTS).

Запустив vmstat, iostat и top, я вижу, что пропускная способность диска (как в TPS, так и в объеме данных) составляет одну цифру%. Точно так же длина очереди диска всегда намного меньше, чем количество потоков, поэтому я не думаю, что я ограничен вводом / выводом. Кроме того, RSS поднимается, но затем стабилизируется на нескольких гигабайтах (как и ожидалось), и vmstat не показывает пейджинг, поэтому я думаю, что я не связан с памятью. Процессор постоянен при 0-1% пользователя, 6-7% системы, а остальные в режиме ожидания. Подсказка! Одно полное «ядро» (не забывайте о гиперпоточности) составляет 6,25% ЦП.

Я знаю, что система отстает, потому что клиентские машины блокируют отправку по протоколу TCP при превышении 64 КБ, и сообщают об этом; все они сообщают об этом факте, и пропускная способность системы намного меньше, чем хотелось бы, предполагалось и теоретически возможно.

Полагаю, я борюсь за какой-то замок. Я использую блокировку уровня приложения для защиты таблицы поиска, которая может быть изменена, поэтому я разбила ее на 256 блокировок / таблиц верхнего уровня, чтобы сломать эту зависимость. Однако это, похоже, совсем не помогло.

Все потоки проходят через один глобальный экземпляр io_service. Запуск strace в приложении показывает, что оно тратит большую часть своего времени на вызовы futex, что, как я полагаю, связано с реализацией реактора io_service на основе четного кода.

Возможно ли, что я ограничил производительность реактора? Как бы я сказал? Насколько дорого и масштабируемо (между потоками) внедрение io_service?

РЕДАКТИРОВАТЬ: Первоначально я не нашел этот другой поток, потому что он использовал набор тегов, которые не перекрывали мои: - / Вполне возможно, моя проблема заключается в чрезмерной блокировке, используемой в реализации реактора boost :: asio , См. Сервер сокетов C ++ - Невозможно насыщать процессор Однако остается вопрос: как я могу доказать это? И как я могу это исправить?

Ответы [ 3 ]

2 голосов
/ 14 октября 2011

Я считаю, что если вы используете несколько объектов io_service (скажем, для каждого ядра процессора), каждый из которых выполняется одним потоком, у вас не возникнет этой проблемы.См. Пример http-сервера 2 на странице поддержки ASIO.

Я провел различные тесты для примера сервера 2 и для примера сервера 3 и обнаружил, что реализация, о которой я говорил, работает лучше всего.

2 голосов
/ 25 августа 2011

Ответ действительно заключается в том, что даже последний boost :: asio вызывает только дескриптор файла epoll из одного потока, не входя в ядро ​​из более чем одного потока одновременно. Я могу понять, почему, потому что безопасность потоков и время жизни объектов чрезвычайно опасны, когда вы используете несколько потоков, каждый из которых может получать уведомления для одного и того же файлового дескриптора. Когда я сам кодирую это (используя pthreads), оно работает и выходит за рамки одного ядра. В этот момент не используется boost :: asio - обидно, что в других случаях хорошо разработанная и переносимая библиотека должна иметь это ограничение.

0 голосов
/ 31 октября 2015

В своем однопоточном приложении из профилирования я узнал, что большая часть инструкций процессора была потрачена на блокировку и разблокировку с помощью io_service :: poll (). Я отключил операции блокировки с помощью макроса BOOST_ASIO_DISABLE_THREADS. Это может иметь смысл и для вас, в зависимости от вашей ситуации с потоками.

...