============================== SObjectizer_ v.4.4 beta6 Notes ============================== :Author: Евгений Охотников :Contact: eao197 at intervale dot ru; eao197 at yahoo dot com :Date: 2008.09.17 .. _SObjectizer: http://sobjectizer.sourceforge.net .. contents:: Введение ======== Этот рассказ продолжает серию описаний, сопровождавших выходы предшествующих 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) | 215K | 415K | | | | | | *10000 агентов в кольце, 100 токенов* | | | +----------------------------------------+-----------+-----------+ | 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) | 117K | 458K | | | | | | *1000 активных агентов* | | | +----------------------------------------+-----------+-----------+ | insend_and_process_msg | 304K | 492K | +----------------------------------------+-----------+-----------+ | ping_pong (l=16, r=10K, ao) | 109K | 134K | | | | | | *два активных агента обмениваются* | | | | *10000 сообщений размером 16 байт* | | | +----------------------------------------+-----------+-----------+ | 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 была достигнута за счет нескольких модификаций, основная часть которых коротко описывается ниже. Использование hash-таблиц вместо std::map ----------------------------------------- Ранее в SObjectizer для организации различных словарей использовался только тип std::map. В beta6 в нескольких местах (глобальный словарь агентов, список активных агентов, список сообщений у агента) вместо std::map теперь используется hash-таблица. В качестве реализации hash-таблицы взят класс ``ACE_Hash_Map_Manager_Ex`` из состава ACE. Это не самая быстрая и удобная в использовании реализация, но зато она не увеличивает списка зависимостей SObjectizer. Со временем можно будет перейти на использование ``unordered_map`` из TR1. Введение понятия dispatcher_binding ----------------------------------- Предшествующие версии SObjectizer использовали централизованную иерархическую схему передачи заявок на выполнение событий рабочим нитям диспетчера. SObjectizer Run-Time знал только одного диспетчера, который был ему передан в ``so_4::api::start``. Этому диспетчеру передавались все заявки, и он должен был разбираться с тем, на какую из рабочих нитей заявка должна быть отправлена. В случае цепочки диспетчеров (наиболее распространенная схема: верхний диспетчер с активными группами, ему подчиняется диспетчер с активными агентами) это приводило к нескольким процедурам диспетчеризации заявки: - сначала в верхнем диспетчере (диспетчер с активными группами проверяет принадлежность агента какой-нибудь активной группе); - затем в подчиненном диспетчере (диспетчер с активными агентами проверяет, является ли агент активным агентом). Подобные проверки требовали захвата mutex-ов, защищавших словари диспетчеров, и поиска имени агента в словарях. И так при диспетчеризации каждого события. В beta6 подход к диспетчеризации событий был пересмотрен. Теперь каждый агент при регистрации в SObjectizer связывается с конкретным контекстом, на котором будут запускаться его события. Проще говоря, агент сразу связывается со своей рабочей нитью. Такая связь реализуется посредством специального интерфейса, называемого ``dispatcher_binding``. После регистрации каждый агент получает указатель на свой ``dispatcher_binding``. При диспетчеризации заявки этот указатель используется SObjectizer-ом для передачи заявки непосредственно рабочей нити агента. Что устранило необходимость дополнительной синхронизации и поиска имени агента по словарям диспетчеров. Изменение типа аргументов send_msg с std::string на string_piece ---------------------------------------------------------------- Изначально функции ``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``). Изменение места вызова message checker-ов ----------------------------------------- В предшествующих версиях 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-ов увеличилось. Если раньше на каждый экземпляр сообщения message checker вызывался всего один раз, то сейчас он вызывается для каждого события, сгенерированного сообщением; - код message checker-а должен быть thread safe, чтобы допускать параллельную работу сразу на нескольких рабочих нитях. Ни одно из данных следствий не имело серьезных последствий на практике, так как все имеющиеся message checker-ы оказались реентерабельными функциями без побочных эффектов. Использование векторов заявок вместо std::map --------------------------------------------- При генерации событий, порождённых сообщением, нужно выделять группы заявок для одного агента, имеющих одинаковый приоритет. Данная группа должна быть преобразована в одну заявку-композит: только одна составляющая этого композита должна быть разрешена к обработке в текущем состоянии агента. Предшествующие версии SObjectizer использовали std::map в процессе формирования списка заявок. Ключом в таком std::map была пара из агента и приоритета события, а значением -- список заявок, который и являлся нужным композитом. Однако std::map имеет серьезные накладные расходы за счет того, что каждый узел дерева поиска (на основе которого и строится std::map) создается в динамической памяти. В beta6 вместо std::map применяется простой вектор заявок. После формирования всех заявок этот вектор сортируется так, чтобы заявки для одного агента с одинаковым приоритетом оказались по соседству. Что позволяет затем одним проходом по результирующему вектору сформировать необходимые заявки-композиты (посредством объединения соседних элементов). Также в beta6 применены оптимизации, которые построены на том, что для сообщения можно определить верхнюю границу числа сгенерированных заявок (по количеству подписчиков сообщения). И если эта граница не очень большая, то вектор для генерируемых заявок создается не в динамической памяти, а на стеке. Уменьшение числа atomic_op при диспетчеризации сообщений -------------------------------------------------------- В beta6 проведена еще одна ревизия операций инкремента/декремента количества ссылок на объекты ``event_data_t``. Теперь эти операции не выполняются, если заявка перемещается между внутренними очередями SObjectizer. Что существенно (иногда до 2-х раз) сократило количество выполняемых atomic_op при диспетчеризации сообщения. Обновление ObjESSty ------------------- Для сериализации данных в SOP протоколе используется библиотека ObjESSty. В beta6 выполнен переход на ObjESSty 1.5, в которой серьезно переработан механизм операций сериализации/десериализации данных. В автономных тестах SObjectizer прирост производительности в этой части не столь заметен, зато в больших приложениях, разработанных на основе SObjectizer, активно передающих сложные структуры данных по SOP-каналам, отмечается прирост производительности в районе 5-10%. Благодарности ============= С удовольствием выражаю благодарность: - Андрею Лабычу за поддержку работ по SObjectizer; - Владиславу Волкову за терпение при внедрении в эксплуатацию разработанных на основе SObjectizer продуктов, а также за настойчивость и последовательность при формировании требований к их развитию; - Дмитрию Вьюкову за незаменимую помощь в объяснении различных явлений, связанных с современными процессорами и памятью, а также за участие в обсуждении различных идей по улучшению SObjectizer; - моих коллег Игоря Мирончика, Леонида Борисенко и Бориса Сивко за плодотворные обсуждения деталей поведения SObjectizer. Вновь хочу поблагодарить всех, кто скачивал SObjectizer и присылал свои отзывы и замечания. Также хочется высказать отдельную благодарность клиентам нашей компании, которые каждый год удваивают нагрузку на наши системы. Что дает очень серьезный стимул, желание и потребность совершенствования SObjectizer и разработанных с его использованием продуктов. .. vim:ts=2:sts=2:sw=2:tw=78:expandtab:ft=rst: