| 05.10.2009 14:00 | |
Статья содержит описание работы по подбору оптимальной архитектуры взаимодействия библиотеки Intel® Threading Building Blocks и библиотеки асинхронного ввода/вывода boost::asio. Рассматриваются возможные проблемы полученных решений, проведено сравнение реализаций при помощи разработанного benchmark-клиента. В качестве примера построено небольшое игровое приложение «MMOLG Точки Online».
Введение
Intel® Threading Building Blocks (TBB) является библиотекой, которая упрощает написание многопоточных приложений, предлагая пользователям C++ дополнительные возможности абстрагирования для развертывания параллельных приложений. В библиотеке TBB разработчики используют привычные шаблоны C++, а сама библиотека отвечает за низкоуровневые детали многопоточности. Кроме того, TBB обеспечивает совместимость с различными архитектурами и операционными системами.
При этом остается ряд задач, решение которых с использованием TBB достаточно сложно и не всегда очевидно, что связано с архитектурой библиотеки. К подобным задачам относится проблема реализации блокирующих операций, в частности операций ввода/вывода. Создание примера решения данной задачи и было предложено в качестве задачи для летней школы.
Что, зачем и для чего?
Планировщик TBB предполагает, что задачи имеют конечный объем работы, не содержат длительной блокировки внутри и их достаточно много. Поэтому нельзя эффективно реализовать блокирующиеся задачи, в частности использующие ввод/вывод.
Наиболее простой выход из этой ситуации - создание отдельного потока для блокирующих операций, но тогда при активном вводе/выводе порождается очень большое количество практически бесполезных потоков, которые ничего не делают, находясь в режиме ожидания. В частности, из-за этого возникает проблема балансировки количества подобных потоков, обслуживающих блокировки, с рабочими потоками TBB.
Для таких задач наиболее оптимально использовать асинхронный ввод/вывод, однако, несмотря на несколько сообщений на форуме Intel о том, что такая архитектура работоспособна, конкретная реализация в публичном доступе отсутствует, и тем более не ясно, насколько эффективно подобное решение.
Для реализации асинхронного ввода/вывода была взята библиотека boost::asio как наиболее распространенная кроссплатформенная разработка.
Архитектура boost::asio
Рисунок 1. Модель ввода/вывода boost::asio
Основной объект boost::asio - это io_service. Он владеет сокетами, обрабатывает запросы на асинхронные операции и вызывает по их завершению предоставленные пользователем callback-методы. Вызов callback-методов происходит в контексте Dispatch-потока, который запрашивает io_service обработать все или несколько завершенных запросов.
Для регистрации операции ввода/вывода и получения списка завершенных запросов io_service использует API конкретной операционной системы, поэтому детали его реализации могут существенно отличаться. Например, в Windows используется overlapped I/O, а в Linux - epoll.
Сервер и его архитектура
Для тестирования полученных решений интеграции ввода/вывода и TBB был взят самый яркий пример программы с активным вводом/выводом - TCP сервер. С одной стороны, большая нагрузка на модуль ввода/вывода - вполне жизненная ситуация, с другой - активные параллельные вычисления на сервере тоже встречаются достаточно часто, и потому использование TBB также не будет лишним.
Рисунок 2. Архитектура сервера
Для облегчения тестирования сервер содержит взаимозаменяемые модули ввода/вывода (в последней версии проекта содержатся наиболее интересные архитектуры - Thread per client, Set of dispatcher threads и Single dispatcher + TBB Pipeline, более подробно о которых будет рассказано ниже).
В целом решение можно считать шаблоном для желающих реализовать сервер с использованием одной из представленных моделей, так как конкретная реализация обработчика запросов может быть легко изменена без правки модуля ввода/вывода.
Синхронная реализация - Thread per client
Для сравнения производительности полученных асинхронных решений была реализована простейшая синхронная архитектура, которой для каждого клиента создается дочерний поток, обрабатывающий его запросы и ожидающий в блокировке, если запросов нет.
Рисунок 3. Временная диаграмма модели Thread per client
Как видно из диаграммы, большую часть времени Dispatch поток, также как и множество обслуживающих клиентов потоков, находятся в ожидании, что является основным недостатком реализации.
Таким образом, к достоинству реализации можно отнести:
- Простота реализации на сокетах Беркли без дополнительных библиотек.
К недостаткам:
- Значительное потребление ресурсов на создание и освобождение потоков при увеличении числа клиентов.
- Проблема балансировки количества обслуживающих клиентов потоков и рабочих потоков TBB.
Асинхронные реализации
Наиболее интересными асинхронными архитектурами среди полученных оказались:
- Set of dispatcher threads
- Single dispatcher + TBB Tasks
- Single dispatcher + TBB Pipeline
Рассмотрим подробно каждую из них.
Set of dispatcher threads
Эта архитектура является в своем роде классической для boost::asio. При запуске сервера создается множество Dispatch потоков, которые обрабатывают результаты запросов и вызывают в своем контексте callback-методы. Они, в свою очередь, обрабатывают запрос и ставят для TBB задачи на выполнение запроса. Затем, если необходимо, инициируется новая асинхронная операция ввода/вывода.
Рисунок 4. Временная диаграмма модели Set of dispatcher threads
Такая модель обладает своими достоинствами:
- Простота реализации на boost::asio.
- Меньшее, чем в синхронной модели, количество потоков и потому лучшее соотношение работа/ожидание.
- Динамическое распределение нагрузки по обработке результатов между потоками.
И недостатками:
- Как и в синхронной реализации, существует проблема балансировки Dispatch потоков с рабочими потоками TBB.
- При обработке результатов несколькими потоками появляются накладные расходы на синхронизацию внутри io_service.
- Dispatch-потоки, которые ставят задачи, не участвуют в их выполнении, поэтому рабочие потоки TBB вынуждены тратить время на их перераспределение между собой. Обработка запроса на задачах TBB (а не только его выполнение) в таких условиях неоправданна и ложится на Dispatch-потоки.
Single dispatcher + TBB Tasks
Эта модель была построена с учетом недостатков предыдущей архитектуры: вместо множества Dispatch потоков используется один, который ставит TBB задачи на обработку и выполнение, а затем сам участвует в работе.
Рисунок 5. Временная диаграмма модели Single dispatcher + TBB Tasks
Видно, что недостатки предыдущей архитектуры переходят в достоинства этой модели:
- Нет накладных расходов на синхронизацию внутри io_service
- Dispatch-поток в рамках callback-методов только формирует очередь задач, обработка и выполнение происходит параллельно рабочими потоками TBB, в том числе и Dispatch-потоком.
- Постановка новых задач производится по окончанию обработки и выполнения - не допускается перегрузка очереди запросов.
Но также возникают свои проблемы:
- Во время обработки задач сервер не опрашивает io_service, и, наоборот, для формировании следующей очереди задач обработка задач должна быть окончена.
Single dispatcher + TBB Pipeline
В предыдущей архитектуре проявился очевидный конвейерный характер обработки и выполнения запросов, для которого более логично использовать параллельный примитив tbb::pipeline, а не tbb:task, поэтому модель Single dispatcher + TBB Tasks является скорее промежуточным звеном на пути к модели Single dispatcher + TBB Pipeline, чем независимым решением.
Первая стадия конвейера (Dispatcher) - работа Dispatch-потока по вызову callback-методов, которые, так же как и в предыдущей модели, формируют очередь задач, однако передают их не TBB Task, а на вторую стадию конвейера (Worker pipeline filter). Такое решение позволяет Dispatch-стадии продолжать опрашивать io_service, не дожидаясь окончания обработки и выполнения задач.
Для того чтобы обеспечить достаточный параллелизм, и, в то же время, не допустить перегрузку очереди запросов, количество токенов tbb::pipeline было ограничено двумя. Тем самым, как и в предыдущей реализации, составление очереди новых событий приостанавливается, если сформировано достаточно работы.
Рисунок 6. Временная диаграмма модели Single dispatcher + TBB Pipeline
Достоинства такой архитектуры, помимо наследованных от предыдущей модели:
- Обработка задач может происходить одновременно с формированием новой очереди.
Преобладание последовательной первой стадии конвейера хорошо видно на рисунках 7, 8. Здесь ось X - время, Y - потоки, фиолетовые блоки - первая последовательная стадия конвейера, остальные блоки (розовые, красные и зеленые) - различные параллельные части второй стадии.
Рисунок 7. Результаты тестирования реализации Single Dispatcher + TBB Pipeline при помощи Jumpshot-4 и прототипа нового коллектора, разрабатываемого Intel, 100 соединений
Рисунок 8. Результаты тестирования реализации Single Dispatcher + TBB Pipeline при помощи Jumpshot-4 и прототипа нового коллектора, разрабатываемого Intel, 10 000 соединений
При большой нагрузке в 10 000 соединений хорошо видно, что большая часть времени уходит на обработку первой последовательной стадии, а потому ее распараллеливание наиболее вероятно приведет к росту производительности.
Методы тестирования
В первую очередь нужно сказать о метриках, которые были использованы при сравнении полученных реализаций. В качестве измеряемых параметров были выбраны следующие:
- Среднее время ответа
(наиболее актуален для пользователя) - Максимально достижимая загрузка
(характеризует устойчивость к пиковым загрузкам)
Для тестирования полученных реализаций был создан специальный benchmark-клиент, позволяющий как обеспечивать пиковую нагрузку на сервер, так и собирать статистику. У данного клиента, также как и у сервера, есть синхронная и асинхронная реализации. Как показало сравнение, более точные значения времени ответа получались с использованием синхронной версии клиента. С другой сторону, асинхронная версия позволяла посылать больше запросов на сервер, что важно для измерения второго параметра - достижимой пиковой загрузки.
Сравнение производительности реализаций
По итогам работы были широко протестирована производительность модулей ввода/вывода 3 наиболее важных реализаций: Thread per client, Set of dispatcher threads и Dispatcher + TBB Pipeline.
Тестирование производилось на 24 ядерном сервере нижегородского офиса Intel, работающего под управлением операционной системы Linux. В качестве результата бралось среднее арифметическое по выборке за 10 минут.
Рисунок 9. Результаты тестирования
Как и ожидалось, синхронная архитектура серьезно проигрывает асинхронным реализациям, между архитектурами Set of dispatcher threads и Dispatcher + TBB Pipeline разница не так значительна (в сравнении с синхронной версией она практически не видна).
Такой результат говорит о хорошей интеграции TBB и boost::asio в последней реализации, так как производилось тестирование только не имеющего вычислительной нагрузки модуля ввода/вывода. Оптимизация TBB для таких задач не является приоритетной, и потому прогнозировалось снижение производительности, однако произошел даже небольшой рост по всем показателям. Кроме того, архитектура Dispatcher + TBB Pipeline тесно интегрирована с TBB, а потому обеспечивает автоматическую балансировку ввода/вывода и вычислительной работы, что может обеспечить лучшее время ответа и/или большую максимально достижимую нагрузку.
Стоит заметить, при аналогичном тестировании на обычном 2 ядерном компьютере с операционной системой Microsoft Windows XP соотношение результатов сильно отличалось от вышеприведенного, а также достаточно часто наблюдалось временное «бездействие» связки benchmark-client + server. Это объясняется особенностями реализации данной операционной системы, в которой практически любое действие пользователя (такое как, например, переключение окон) сказывается на результатах тестирования, а также работой антивируса.
Игровая составляющая
Результатом решения задачи летней школы должен был стать открытый пример интеграции библиотек Intel TBB и boost::asio. Для более эффективной демонстрации созданных архитектур было решено реализовать простейшую логику игры «Точки». Стоит отметить, что данная игровая составляющая также позволяет создавать вычислительную нагрузку и моделировать более жизненные ситуации, в которых сервер динамически создает html-страницы (а не только отсылает готовые), а также выполняет некоторую логику.
Вся игровая механика была реализована как отдельная статическая библиотеке, которая хранит состояние всех игр и по запросам пользователя создает нужную html-страницу. Для реализации взаимодействия с пользователем были использованы JavaScript вставки, которые проверяли корректность действий и формировали запросы для сервера.
Рисунок 10. Интерфейс игрового клиента
В результате этого заинтересовавшиеся данным примером разработчики смогут не только увидеть сухие цифры benchmark-клиента, но и через браузер посмотреть, как будет себя вести сервер при пиковой нагрузке.
Итоги и дальнейшие пути TBB в асинхронном мире
По итогам проекта были достигнуты основные цели: разработаны архитектуры взаимодействия TBB и ввода/вывода, рассмотрены возможные проблемы полученных решений, проведено сравнение реализаций при помощи разработанного benchmark-клиента, в качестве примера построено небольшое игровое приложение.
Конечно, архитектура Dispatcher + TBB Pipeline не является законченной и существует множество мелких и крупных дальнейших улучшений, которые не были первоочередными, и на реализацию которых не хватило времени в рамках летней школы. Так, возможна статическая балансировка подключений между несколькими io_service, что может увеличить максимально возможную пиковую нагрузку сервера.
Если рассматривать пути дальнейшего развития серии примеров в целом, то наиболее продуктивным выглядит прямое использование интерфейсов ввода/вывода, предоставляемых операционной системой, без использования дополнительных слоев абстракции, накладывающих свои ограничения.
Результат такого развития будет скорее не примером, а уже библиотекой, предоставляющей программисту простые интерфейсы работы с асинхронным вводом/выводом, основанные на TBB и обеспечивающие тесную и прозрачную интеграцию TBB и асинхронного ввода/вывода.
Все разработанное решение, содержащее реализацию рассмотренных архитектур, benchmark-клиент, игровой модуль, а также историю развития можно найти на портале проекта: http://tbbdots.codeplex.com.
Об авторах
Сергей Гризан, студент Сибирского Федерального Университета, Красноярск, проходил стажировку в рамках Летней Школы Intel в Нижнем Новгороде, которая оказалась интереснейшей и полезной заменой традиционного летнего отдыха. В ходе стажировки я получил неоценимый опыт работы по проекту, общения с отличными профессионалами и просто хорошими, интересными людьми, а также узнал множество полезной информации на лекциях и практических занятиях. В рамках проекта занимался разработкой интерфейсов, архитектуры решения в целом, а так же множества реализаций сервера.
Максим Кривов, студент 5-ого курса факультета Вычислительной Математики и Кибернетики МГУ им. М.В. Ломоносова. От прохождения стажировки в Нижнем Новгороде осталась масса впечатлений, которые точно запомнятся на всю жизнь. При решении задачи летней школы отвечал за разработку benchmark-клиента и реализацию игровой составляющей.
Также благодарим Антона Малахова и Mike Voss за предоставленные дополнительные материалы.
Пожалуйста, обратитесь к странице Уведомление об оптимизации для более подробной информации относительно производительности и оптимизации в программных продуктах компании Intel.
Комментарии (4) 
| 23.10.2009 05:02
Dmitry Oganezov (Intel)
|
Актуальная тема эффективного распараллеливания задач, имеющих большую долю блокирующих операций. Отличное введение, да и представленные результаты выглядят весьма практичным, а выводы - обоснованными. Чего не хватает? Массовому читателю, думаю, интересно было бы узнать некоторые детали о TBB и boost::asio. В чем, к примеру, разница между tasks и pipeline? Резюмируя: хорошая тема и интересная методика. Команда TBB, вы уже сделали Сергею предложение? :) |
| 23.10.2009 07:01
life1ess
|
2Nikolay Kurtov: в правилах оформления было сказано, что работа должна называться так же, как тема летней школы, а так, конечно, заголовок несколько нерелевантный. 2Dmitry Oganezov: видимо я все-таки ориентировался на человека с некоторым background по TBB и не подумал о массовом читателе. Как я это понимаю, tasks предназначены для независимых задач, которые выполняют некоторые операции и не взаимодействуют друг с другом, только с главным процессом; в случае pipeline (т.е. конвейера) обработка разделена на некоторые стадии, вывод одной стадии становится вводом следующей, при этом работа происходит параллельно (в том числе, если указан соотвествующий флаг, то параллельно могут работать и несколько экземпляров одной стадии) |
| 27.10.2009 06:27
Anton Malakhov (Intel)
| Поздравляю с победой в конкурсе! Мне это так же приятно как и вам :) |
Обратная ссылка (1)
- Блоги Intel® Software Network » Лето с Intel для профессионального роста: знакомьтесь с победителями!
27.10.2009 01:17




Nikolay Kurtov (Intel)
15