Author: | Евгений Охотников |
---|---|
Contact: | eao197 at intervale dot ru; eao197 at yahoo dot com |
Date: | 2008.09.17 |
Этот рассказ продолжает серию описаний, сопровождавших выходы предшествующих beta-версий SObjectizer 4.4 (beta3 и beta5).
В очередной, шестой, beta-версии удалось воплотить в жизнь несколько идей, которые сформировались на основании опыта использования SObjectizer 4.4.0-beta5 в ряде проектов компании Интервэйл. В результате чего была существенно увеличена производительность, а также добавлена возможность использования нескольких реакторов в SObjectizer приложениях.
Вот результаты тестов, замеряющих производительность SObjectizer 4.4.beta5 и 4.4.beta6 (Intel Core2Duo 2GHz, 2Gb Ram, WinXP SP2, Visual C++ 7.1 SP1):
Название теста | beta5 | beta6 |
chameneos | 313K | 492K |
customer_ring (N=10K, M=100) 10000 агентов в кольце, 100 токенов |
215K | 415K |
customer_ring (N=10K, M=100, AG=2) две активные группы |
200K | 170K |
customer_ring (N=10K, M=100, AG=4) | 173K | 251K |
customer_ring (N=10K, M=100, AG=8) | 167K | 306K |
customer_ring (N=10K, M=100, AG=16) | 166K | 351K |
customer_ring (N=1K, M=100, AO) 1000 активных агентов |
117K | 458K |
insend_and_process_msg | 304K | 492K |
ping_pong (l=16, r=10K, ao) два активных агента обмениваются 10000 сообщений размером 16 байт |
109K | 134K |
process_state_changes | 110K/160K | 160K/182K |
send_and_process_msg | 377K/800K | 709K/917K |
send_msg | 598K | 1280K |
Пояснение 1. Значения указываются в тысячах сообщений/событий в секунду. Т.е. значение 315K -- это 315 тысяч сообщений в секунду.
Пояснение 2. В тестах process_state_changes и send_and_process_msg замеряются две фазы -- отсылка сообщений и обработка сообщений. Первое из приведенных чисел обозначает скорость работы на первой фазе, второе -- на второй фазе. Так, значения 160K/182K указывают, что скорость отсылки сообщений составляла 160 тысяч сообщений в секунду, а скорость обработки сообщений -- 182 тысячи событий в секунду.
Пояснение 3. В тесте customer_ring с двумя активными группами показатели beta6 хуже показателей beta5 только на указанной выше машине с Core2Duo. На одноядерных и 4-хядерных машинах, на которых также были проведены соответствующие замеры, beta6 была стабильно быстрее beta5. Видимо, это тот случай, когда два ядра на тесте customer_ring настолько быстро обрабатывают события тестовых агентов, что большую часть времени ядра процессора проводят на синхронизации доступа к каким-то разделяемым структурам SObjectizer или run-time библиотеки C++. Устранение этого "бутылочного горлышка" будет задачей следующих бета-версий SObjectizer.
В beta5 был совершен окончательный переход на использование средств библиотеки ACE в реализации транспортных агентов. За диспетчеризацию событий ввода-вывода в beta5 отвечал единственный объект ACE_Select_Reactor, который являлся реактором по умолчанию. Это означало, что если в приложении создается много транспортных каналов, то они все обрабатываются с помощью этого реактора. Единственным способом смены реактора была замена реактора по умолчанию с помощью метода ACE_Reactor::instance.
В beta6 в SObjectizer было добавлено понятие реестра реакторов. Программист может заполнить реестр тем количеством реакторов, которое ему необходимо. При старте SObjectizer Run-Time все реакторы из этого реестра будут запущены, а при завершении работы SObjectizer Run-Time -- остановлены.
Каждый реактор в реестре идентифицируется собственным уникальным именем. Это дает возможность найти нужный реактор в реестре и привязать транспортного агента к данному реактору:
so_4::rt::comm::a_sop_incoming_channel_processor_t a_channel( "a_channel", // Для задания реактора нужно использовать функцию // create_acceptor_controller со всеми параметрами. so_4::transport_layer::socket::create_acceptor_controller( so_4::transport_layer::socket::acceptor_params( ip_address ), so_4::transport_layer::channel_params_t(), so_4::transport_layer::socket::option_setter_auto_ptr_t(), // Вот и реактор, на котором предстоит работать каналу. so_4::ace::reactor_registry().find( "a_channel_reactor" ) ) );
В SObjectizer 4.4 beta6 предоставляются готовые реализации реакторов ACE_Select_Reactor и ACE_TP_Reactor (аналог Select_Reactor-а, работающий на пуле потоков), которые могут использоваться с реестром реакторов.
Повышение производительности beta6 была достигнута за счет нескольких модификаций, основная часть которых коротко описывается ниже.
Ранее в SObjectizer для организации различных словарей использовался только тип std::map. В beta6 в нескольких местах (глобальный словарь агентов, список активных агентов, список сообщений у агента) вместо std::map теперь используется hash-таблица.
В качестве реализации hash-таблицы взят класс ACE_Hash_Map_Manager_Ex из состава ACE. Это не самая быстрая и удобная в использовании реализация, но зато она не увеличивает списка зависимостей SObjectizer. Со временем можно будет перейти на использование unordered_map из TR1.
Предшествующие версии SObjectizer использовали централизованную иерархическую схему передачи заявок на выполнение событий рабочим нитям диспетчера. SObjectizer Run-Time знал только одного диспетчера, который был ему передан в so_4::api::start. Этому диспетчеру передавались все заявки, и он должен был разбираться с тем, на какую из рабочих нитей заявка должна быть отправлена.
В случае цепочки диспетчеров (наиболее распространенная схема: верхний диспетчер с активными группами, ему подчиняется диспетчер с активными агентами) это приводило к нескольким процедурам диспетчеризации заявки:
Подобные проверки требовали захвата mutex-ов, защищавших словари диспетчеров, и поиска имени агента в словарях. И так при диспетчеризации каждого события.
В beta6 подход к диспетчеризации событий был пересмотрен. Теперь каждый агент при регистрации в SObjectizer связывается с конкретным контекстом, на котором будут запускаться его события. Проще говоря, агент сразу связывается со своей рабочей нитью. Такая связь реализуется посредством специального интерфейса, называемого dispatcher_binding.
После регистрации каждый агент получает указатель на свой dispatcher_binding. При диспетчеризации заявки этот указатель используется SObjectizer-ом для передачи заявки непосредственно рабочей нити агента. Что устранило необходимость дополнительной синхронизации и поиска имени агента по словарям диспетчеров.
Изначально функции send_msg получали два типа аргументов: const char * и const std::string &. Но в конце концов, управление передавалось функции send_msg, с аргументами типа const std::string &. Это означало, что в случае, например, вызова:
so_4::api::send_msg( "some_agent_name", "msg_some_message" );
Для аргументов создавалось два объекта std::string, что могло сопровождаться динамическим выделением памяти, если конкретная реализация std::string не поддерживала оптимизацию для маленьких строк.
В beta6 вместо аргументов const char * и const std::string & используется аргументы типа string_piece_t. Этот тип является маленькой структурой, которая содержит только указатель на начало строки и ее длину. Создание таких структур занимает очень мало времени (самые большие накладные расходы в случае const char * -- это обращение к std::strlen).
В предшествующих версиях SObjectizer функция проверки корректности сообщения (т.н. message checker) вызывался внутри send_msg перед генерацией событий. Поскольку message checker мог выполнять совершенно любые действия, то перед вызовом message checker-а send_msg выполнял разблокирование ядра SObjectizer Run-Time, а затем блокирование ядра для генерации заявок. Эта пара из разблокирования/блокирования ядра при вызове send_msg ощутимо сказывалось на производительности операции send_msg.
В beta6 вызов message checker-а отложен до момента вызова обработчика события на контексте рабочей нити. Что позволило отказаться от лишней разблокировки/блокировки ядра в send_msg. А это, в свою очередь, существенно подняло скорость работы send_msg.
Такое перемещение места вызова message checker-а, однако, привело к двум важным следствиям:
Ни одно из данных следствий не имело серьезных последствий на практике, так как все имеющиеся message checker-ы оказались реентерабельными функциями без побочных эффектов.
При генерации событий, порождённых сообщением, нужно выделять группы заявок для одного агента, имеющих одинаковый приоритет. Данная группа должна быть преобразована в одну заявку-композит: только одна составляющая этого композита должна быть разрешена к обработке в текущем состоянии агента.
Предшествующие версии SObjectizer использовали std::map в процессе формирования списка заявок. Ключом в таком std::map была пара из агента и приоритета события, а значением -- список заявок, который и являлся нужным композитом. Однако std::map имеет серьезные накладные расходы за счет того, что каждый узел дерева поиска (на основе которого и строится std::map) создается в динамической памяти.
В beta6 вместо std::map применяется простой вектор заявок. После формирования всех заявок этот вектор сортируется так, чтобы заявки для одного агента с одинаковым приоритетом оказались по соседству. Что позволяет затем одним проходом по результирующему вектору сформировать необходимые заявки-композиты (посредством объединения соседних элементов).
Также в beta6 применены оптимизации, которые построены на том, что для сообщения можно определить верхнюю границу числа сгенерированных заявок (по количеству подписчиков сообщения). И если эта граница не очень большая, то вектор для генерируемых заявок создается не в динамической памяти, а на стеке.
В beta6 проведена еще одна ревизия операций инкремента/декремента количества ссылок на объекты event_data_t. Теперь эти операции не выполняются, если заявка перемещается между внутренними очередями SObjectizer. Что существенно (иногда до 2-х раз) сократило количество выполняемых atomic_op при диспетчеризации сообщения.
Для сериализации данных в SOP протоколе используется библиотека ObjESSty. В beta6 выполнен переход на ObjESSty 1.5, в которой серьезно переработан механизм операций сериализации/десериализации данных. В автономных тестах SObjectizer прирост производительности в этой части не столь заметен, зато в больших приложениях, разработанных на основе SObjectizer, активно передающих сложные структуры данных по SOP-каналам, отмечается прирост производительности в районе 5-10%.
С удовольствием выражаю благодарность:
Вновь хочу поблагодарить всех, кто скачивал SObjectizer и присылал свои отзывы и замечания.
Также хочется высказать отдельную благодарность клиентам нашей компании, которые каждый год удваивают нагрузку на наши системы. Что дает очень серьезный стимул, желание и потребность совершенствования SObjectizer и разработанных с его использованием продуктов.