Здравствуйте, remark, Вы писали:
R>Если будет время может попробую сделать патч к SObjectizer как я себе это вижу...
Я заинвестегировал процесс отправки/приёма сообщений с т.з. синхронизации в SObjectizer. Получается следующая картина:
Отправка сообщения:
shutdown_preventer: rw_mutex — при входе lock/unlock, при выходе lock/unlock (4)
kernel_t::read_access_t: аналогично (4)
создание msg_data_t: new + 3 * lock/unlock (8)
ещё раз kernel_t::read_access_t (4)
создание евента/рутинг/помещение в очередь: 2 * new + 7 * lock/unlock (на каждый евент) (18)
Получение и обработка евента:
3 * lock/unlock (6)
под lock/unlock я подразумеваю захват и освобождение обычного мьютекса (критической секции под win)
под new можно подразумевать тоже самое с т.з. синхронизации
Итого получается на одно сообщение: (20 + 24 * event_count) входов или выходов из мьютекса
если event_count == 1, то 44 interlocked операции
Часть мьютексов можно убрать даже без особых ухищрений.
Вначале отправки захватывается kernel_t::read_access_t, потом отпускается и сразу захватывается снова — можно его уж и не отпускать, тогда убирается 2 захвата и освобождения мьютекса. Правда вопрос — не начнутся ли дедлоки...
Вначале отправки захватывается shutdown_preventer и примерно с ним же захватывается kernel_t::read_access_t. Если их объединить в один, то убирается ещё 2 захвата и освобождения мьютекса.
При создании msg_data_t отрабатывает конструктор копирования и деструктор, которые лишний раз инкремекнтируют и декремекнтируют счётчики. Если создавать объект поместу — без конструктора копирования и лишнего деструктора, то опять же убирается 2 захвата и освобождения мьютекса.
Итого 12 из 44 элиминировали... осталось 32
При создании и рутинге евента тоже можно убрать лишние инкременты и декременты счётчиков — там 3 раза счётчики инкрементируются и потом сразу 2 раза декрементируются. Скорее всего это избыточные операции, т.е. должен быть только один инкремент. Т.о. убирается ещё 4 захвата и освобождения мьютекса.
Здравствуйте, remark, Вы писали:
R>Я тут такой провёл эксперимент. R>Запустил бенчмарк customer_ring (ring size: 10000; token count: 100; dispatcher: one thread) — получил пропускную способность 162000. R>Потом убрал в функции send_msg() rw_mutex so_4::rt::impl::kernel_t::shutdown_preventer_t shutdown_preventer R>получил пропускную способность 163000 R>Потом убрал мьютексы в функциях agent_wrapper_base_t::inc_ref_count()/agent_wrapper_base_t::dec_ref_count() (те самые, которые считают референс каунтеры для сообщений/событий/агентов) R>получил пропускную способность 167000
У меня, если закомментировать код методов class_handler_impl_t::start_ref_count_op() и class_handler_impl_t::finish_ref_count_op() получаются такие результаты:
тест customer_ring: 160000 против 142500
тест send_msg: 438000 против 392000
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, remark, Вы писали:
R>Я заинвестегировал процесс отправки/приёма сообщений с т.з. синхронизации в SObjectizer. Получается следующая картина:
Да, не слабую ты работу проделал! Внушаить
R>Отправка сообщения: R>shutdown_preventer: rw_mutex — при входе lock/unlock, при выходе lock/unlock (4) R>kernel_t::read_access_t: аналогично (4) R>создание msg_data_t: new + 3 * lock/unlock (8) R>ещё раз kernel_t::read_access_t (4) R>создание евента/рутинг/помещение в очередь: 2 * new + 7 * lock/unlock (на каждый евент) (18)
R>Получение и обработка евента: R>3 * lock/unlock (6)
R>под lock/unlock я подразумеваю захват и освобождение обычного мьютекса (критической секции под win) R>под new можно подразумевать тоже самое с т.з. синхронизации
R>Итого получается на одно сообщение: (20 + 24 * event_count) входов или выходов из мьютекса R>если event_count == 1, то 44 interlocked операции
R>Часть мьютексов можно убрать даже без особых ухищрений. R>Вначале отправки захватывается kernel_t::read_access_t, потом отпускается и сразу захватывается снова — можно его уж и не отпускать, тогда убирается 2 захвата и освобождения мьютекса. Правда вопрос — не начнутся ли дедлоки...
R>Вначале отправки захватывается shutdown_preventer и примерно с ним же захватывается kernel_t::read_access_t. Если их объединить в один, то убирается ещё 2 захвата и освобождения мьютекса.
Нет, дедлоки не начнутся. Но два захвата kernel_t::read_access_t нужны для того, чтобы запускать checker-ов сообщений (если таковые есть) при незаблокированном ядре. Поскольку никаких ограничений на код checker-а нет (и вряд ли может быть), то checker может надолго заблокировать ядро, что не даст другим фрагментам программы работать с SObjectizer.
Однако, в течении все операции отсылки сообщения операция shutdown должна быть запрещена, поэтому shutdown_preventer должен быть независимым от kernel_t::read_access_t. Другое дело, что, возможно, shutdown_preventer можно через atomic-операции и condition variable реализовать. Может быть, будет дешевле.
R>При создании msg_data_t отрабатывает конструктор копирования и деструктор, которые лишний раз инкремекнтируют и декремекнтируют счётчики. Если создавать объект поместу — без конструктора копирования и лишнего деструктора, то опять же убирается 2 захвата и освобождения мьютекса.
R>При создании и рутинге евента тоже можно убрать лишние инкременты и декременты счётчиков — там 3 раза счётчики инкрементируются и потом сразу 2 раза декрементируются. Скорее всего это избыточные операции, т.е. должен быть только один инкремент. Т.о. убирается ещё 4 захвата и освобождения мьютекса.
Да, инкременты в msg_data_t и event_data_t составляют сейчас самое узкое место. Если от них избавиться, то можно убрать существенный оверхед.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Да, не слабую ты работу проделал! Внушаить E>
Продебажил просто
E>Нет, дедлоки не начнутся. Но два захвата kernel_t::read_access_t нужны для того, чтобы запускать checker-ов сообщений (если таковые есть) при незаблокированном ядре. Поскольку никаких ограничений на код checker-а нет (и вряд ли может быть), то checker может надолго заблокировать ядро, что не даст другим фрагментам программы работать с SObjectizer.
Можно вынести проверку сообщения вообще за критический регион.
Там же нужно только само пользовательское сообщение и функция проверки. Я видел, что функция проверки ищется через системный словарь, но ведь фактически она известна априори — можно как-то сделать, что бы её не надо было искать через словарь. Тогда можно сделать один rw_lock вместо трёх...
Может меняться в ран-тайм. Или эти все данные генерируются при старте и потом при работе не меняются. Я имею в виду появление новых типов агентов, сообщений или событий, а так же их изменение и удаление.
Если не меняется, то всю эту информацию можно вынести в константный глобальный объект, для доступа к которому не надо мьютексов.
E>Однако, в течении все операции отсылки сообщения операция shutdown должна быть запрещена, поэтому shutdown_preventer должен быть независимым от kernel_t::read_access_t. Другое дело, что, возможно, shutdown_preventer можно через atomic-операции и condition variable реализовать. Может быть, будет дешевле.
Если сделать предыдущий пункт, то их можно объединить...
R>>При создании msg_data_t отрабатывает конструктор копирования и деструктор, которые лишний раз инкремекнтируют и декремекнтируют счётчики. Если создавать объект поместу — без конструктора копирования и лишнего деструктора, то опять же убирается 2 захвата и освобождения мьютекса.
R>>При создании и рутинге евента тоже можно убрать лишние инкременты и декременты счётчиков — там 3 раза счётчики инкрементируются и потом сразу 2 раза декрементируются. Скорее всего это избыточные операции, т.е. должен быть только один инкремент. Т.о. убирается ещё 4 захвата и освобождения мьютекса.
E>Да, инкременты в msg_data_t и event_data_t составляют сейчас самое узкое место. Если от них избавиться, то можно убрать существенный оверхед.
Там надо аккуратно переписать. что бы не делать лишних парных инкрементов/декрементов — я вроде таких 3 штуки насчитал при отправке...
Здравствуйте, remark, Вы писали:
E>>Нет, дедлоки не начнутся. Но два захвата kernel_t::read_access_t нужны для того, чтобы запускать checker-ов сообщений (если таковые есть) при незаблокированном ядре. Поскольку никаких ограничений на код checker-а нет (и вряд ли может быть), то checker может надолго заблокировать ядро, что не даст другим фрагментам программы работать с SObjectizer.
R>Можно вынести проверку сообщения вообще за критический регион. R>Там же нужно только само пользовательское сообщение и функция проверки. Я видел, что функция проверки ищется через системный словарь, но ведь фактически она известна априори — можно как-то сделать, что бы её не надо было искать через словарь. Тогда можно сделать один rw_lock вместо трёх...
Сейчас в SObjectizer нужно по имени сообщения найти его тип и уже там функцию checker. А для этого нужно обратиться в системный словарь.
Возможно, этой проблемы бы не было, если бы все сообщения наследовались от какого-нибудь базового класса. Тогда checker мог бы быть виртуальным методом, который можно вызвать без системного словаря.
R>Кстати вот эта информация:
R>
R>Может меняться в ран-тайм. Или эти все данные генерируются при старте и потом при работе не меняются. Я имею в виду появление новых типов агентов, сообщений или событий, а так же их изменение и удаление. R>Если не меняется, то всю эту информацию можно вынести в константный глобальный объект, для доступа к которому не надо мьютексов.
Описание одного класса агента после того, как оно построено, уже не может быть изменено. Но вот количество классов в системном словаре может меняться во время работы программы -- при ручной загрузке и выгрузке DLL.
Кроме того, схема формирования описания класса агента такова, что само описание в системном словаре уже в run-time. Поскольку данные макросы раскрываются в набор глобальных переменных, каждая из которых добавляет свою часть информации в описание класса по частям.
Так что идея иметь read-only словарь, который может всеми читаться без блокировок, мне очень симпатична. Если бы еще знать, как его реализовать.
E>>Да, инкременты в msg_data_t и event_data_t составляют сейчас самое узкое место. Если от них избавиться, то можно убрать существенный оверхед.
R>Там надо аккуратно переписать. что бы не делать лишних парных инкрементов/декрементов — я вроде таких 3 штуки насчитал при отправке...
Вот мне пока не удалось за ограниченное время придумать более аккуратную и жизнеспособную схему для этих инкрементов/декрементов
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Как оказалось, смухлевал не немного, а много E>Вот версия, которая использует два мутекса (по два счетчика под каждым). А так же содержит тест TLS (в терминологии ACE -- TSS).
Попробуй так, что бы увидеть tss raw performance
#include <windows.h>
class object_with_refcount_t
{
public :
virtual ~object_with_refcount_t();
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, remark
E>Появится время, попробую.
E>Дима, я не знаю, читает ли эту тему кто-нибудь, кроме нас. Может переместим такие технические обсуждения на SourceForge? Вот сюда: http://sourceforge.net/forum/forum.php?forum_id=550088
Здравствуйте, eao197, Вы писали:
R>>Запустил бенчмарк customer_ring (ring size: 10000; token count: 100; dispatcher: one thread) — получил пропускную способность 162000. R>>Потом убрал в функции send_msg() rw_mutex so_4::rt::impl::kernel_t::shutdown_preventer_t shutdown_preventer R>>получил пропускную способность 163000 R>>Потом убрал мьютексы в функциях agent_wrapper_base_t::inc_ref_count()/agent_wrapper_base_t::dec_ref_count() (те самые, которые считают референс каунтеры для сообщений/событий/агентов) R>>получил пропускную способность 167000
E>У меня, если закомментировать код методов class_handler_impl_t::start_ref_count_op() и class_handler_impl_t::finish_ref_count_op() получаются такие результаты: E>тест customer_ring: 160000 против 142500 E>тест send_msg: 438000 против 392000
Немного покурил над msg_data_t. Обнаружил, что от одной операции инкремента/декремента вполне можно избавиться. Избавился и теперь получается:
тест customer_ring: 154000 против 142500;
тест send_msg: 435000 против 392000.
Кстати, я отладочными печатями и отладочными счетчиками подсчитал, что на каждое сообщение+событие (в тестах customer_ring и send_and_process_msg) выполняется 8 операций инкремента/декремента количества ссылок.
В текущей схеме я уже не представляю, как от этого избавиться.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, eao197, Вы писали:
R>>>Запустил бенчмарк customer_ring (ring size: 10000; token count: 100; dispatcher: one thread) — получил пропускную способность 162000. R>>>Потом убрал в функции send_msg() rw_mutex so_4::rt::impl::kernel_t::shutdown_preventer_t shutdown_preventer R>>>получил пропускную способность 163000 R>>>Потом убрал мьютексы в функциях agent_wrapper_base_t::inc_ref_count()/agent_wrapper_base_t::dec_ref_count() (те самые, которые считают референс каунтеры для сообщений/событий/агентов) R>>>получил пропускную способность 167000
E>>У меня, если закомментировать код методов class_handler_impl_t::start_ref_count_op() и class_handler_impl_t::finish_ref_count_op() получаются такие результаты: E>>тест customer_ring: 160000 против 142500 E>>тест send_msg: 438000 против 392000
E>Немного покурил над msg_data_t. Обнаружил, что от одной операции инкремента/декремента вполне можно избавиться. Избавился и теперь получается:
Ты случаем исправил не тоже, что я уже у себя исправил:
в функции send_msg() создание сообщения я исправил, т.ч тут не отрабатывает конструктор копирования и исчезает одна лишняя пара икремент/декремент:
to_deliver.reset( // <-------------------
new so_4::rt::impl::msg_data_impl_t(
msg_data, msg_wrapper, receiver,
channel_to,
// В качестве отправителя — localhost
so_4::rt::comm_channel_t::localhost(),
true), // <-------------------
false); // <-------------------
Я тут сразу создаю msg_data_impl_t со счётчиком = 1 и инкрементирую счётчик агента атомарной операцией
E>тест customer_ring: 154000 против 142500; E>тест send_msg: 435000 против 392000.
Чо-то я не понял, что ты убрал — стало ж только медленнее
Здравствуйте, remark, Вы писали:
R>Ты случаем исправил не тоже, что я уже у себя исправил:
Именно то
Этот фрагмент используется не только в send_msg, но и при обработке входящего SOP-пакета send_msg.
Но я сделал в msg_data_t оператор копирования, который получает msg_data_impl_t *.
Пока без атомарных операций.
E>>тест customer_ring: 154000 против 142500; E>>тест send_msg: 435000 против 392000.
R>Чо-то я не понял, что ты убрал — стало ж только медленнее
Ну как же медленее
Большие числа -- это показатели новой версии.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, remark, Вы писали:
R>>Ты случаем исправил не тоже, что я уже у себя исправил:
E>Именно то E>Этот фрагмент используется не только в send_msg, но и при обработке входящего SOP-пакета send_msg. E>Но я сделал в msg_data_t оператор копирования, который получает msg_data_impl_t *. E>Пока без атомарных операций.
А я сделал функцию reset(), что б лишний раз не копировать...
И я сделал у конструктора msg_data_t доп. параметр, что б сразу устанавливать счётчик в 1...
E>>>тест customer_ring: 154000 против 142500; E>>>тест send_msg: 435000 против 392000.
R>>Чо-то я не понял, что ты убрал — стало ж только медленнее
E>Ну как же медленее E>Большие числа -- это показатели новой версии.
E>У меня, если закомментировать код методов class_handler_impl_t::start_ref_count_op() и class_handler_impl_t::finish_ref_count_op() получаются такие результаты:
E>тест customer_ring: 160000 против 142500
E>тест send_msg: 438000 против 392000
Немного покурил над msg_data_t. Обнаружил, что от одной операции инкремента/декремента вполне можно избавиться. Избавился и теперь получается:
тест customer_ring: 154000 против 142500;
тест send_msg: 435000 против 392000.
Первые числа не изменились, а вторые не изменились... ... либо мне спать надо больше?
Здравствуйте, remark, Вы писали:
E>>Большие числа -- это показатели новой версии.
R>
E>>У меня, если закомментировать код методов class_handler_impl_t::start_ref_count_op() и class_handler_impl_t::finish_ref_count_op() получаются такие результаты:
E>>тест customer_ring: 160000 против 142500
E>>тест send_msg: 438000 против 392000
R>Немного покурил над msg_data_t. Обнаружил, что от одной операции инкремента/декремента вполне можно избавиться. Избавился и теперь получается:
R>тест customer_ring: 154000 против 142500;
R>тест send_msg: 435000 против 392000.
R>Первые числа не изменились, а вторые не изменились... ... либо мне спать надо больше?
Если брать тест customer_ring, то значение 142500 -- это показатель версии 4.4.b3 (последней официальной беты). Значение 160000 получается, если в class_handler_impl_t закомментировать операции с мутексами, т.е. когда блокировок вообще нет. А значение 154000 получается в текущей версии после удаления одной лишней блокировки.
Вообще, замеры сильно отличаются в зависимости от того, сколько времени работала WinXP без перезагрузки. Если сразу после старта WinXP, то получается высокая скорость. После трех-четырех часов работы скорость заметно снижается. Так что к замерам нужно относиться с некоторой степенью недоверия.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, remark, Вы писали:
R>>>Хм... а почему нельзя использовать Interlocked-операции?
E>>Потому что изменение счетчика ссылок на сообщение должно выполняться в одной же транзакции с изменением счетчика ссылок на агента-владельца. А иногда и еще какой-то счетчик задействуется (если не забыл ничего). Т.е. за раз нужно изменять сразу несколько ссылок.
R>А почему это должно быть именно в одной транзакции? Почему никто не должен видеть эти счётчики в рассогласованных состояниях? Ведь с reference counting'ом обычно так — или в счётчике не ноль и тогда собственно пофигу что, т.к. объект не удаляется, или в счётчике 0, и тогда вобщем-то тоже пофигу, т.к. объект удаляется...
Дима, спасибо за эту подсказку!
Я сделал счетчики ссылок на основе ACE_Atomic_Op. Причем вот с такой оптимизацией: счетчик ссылок на агента изменяется только два раза:
1) при первом инкременте ссылки на msg_data_impl_t/event_data_impl_t (т.е. когда эти объекты только были созданы);
2) при последнем декременте ссылки на msg_data_impl_t/event_data_impl_t (т.е. когда эти объекты будут уничтожены).
Все остальные инкременты/декременты msg_data_impl_t/event_data_impl_t уже не оказываеют влияния на счетчик ссылок агента.
Такая оптимизация должна особенно сильно сказаться в случаях, когда msg_data_impl_t тиражируется (большое количество подписчиков на сообщение или сообщение является отложенным/периодическим).
Если же ACE для какой-то платформы не предоставляет эффективной реализации ACE_Atomic_Op, то используется старая схема с мутексом.
На моем нуотбуке в тесте customer_ring прирост составил порядка 14K msg/sec по сравнении с версией 4.4.b3 (т.е. где-то 160K-158K в текущей версии против 142K-146K в v.4.4.b3). На тесте send_and_process_msg производительность send_msg увеличилась где-то на 10K msg/sec (266K в текущей версии против 255K в v.4.4.b3), а обработка событий вообще на 40K evt/sec (492K в текущей версии против 456K в v.4.4.b3).
Я вот что думаю -- может эксперименты по выжиманию скорости из send_msg() пока прекратить. Для версии 4.4.b4 есть еще одна очень важная задача -- изменения способа работы с транспортными каналами, т.к. текущая схема очень проста и неэффективна. Я думаю сосредоточится сейчас именно на транспортном слое. Это и будет версия 4.4.b4. Если же за это время появятся хорошие, не сложные и жизнеспособные идеи по дальнейшей оптимизации send_msg(), то их можно будет попробовать воплотить в 4.4.b5.
Ты как думаешь? (Может еще кто-нибудь из читателей форума выскажется?)
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Если брать тест customer_ring, то значение 142500 -- это показатель версии 4.4.b3 (последней официальной беты). Значение 160000 получается, если в class_handler_impl_t закомментировать операции с мутексами, т.е. когда блокировок вообще нет. А значение 154000 получается в текущей версии после удаления одной лишней блокировки.
Всё понял.
E>Вообще, замеры сильно отличаются в зависимости от того, сколько времени работала WinXP без перезагрузки. Если сразу после старта WinXP, то получается высокая скорость. После трех-четырех часов работы скорость заметно снижается. Так что к замерам нужно относиться с некоторой степенью недоверия.
Я у себя мерил на одноядерном P4 — у меня примерно такая же картина получилась — стабильно пропускная способность увеличилась на 6-7%. Т.ч. Я думаю, что так оно примерно и есть.
Вот ведь, а кажется убрал всего ничего...
Здравствуйте, eao197, Вы писали:
E>Дима, спасибо за эту подсказку!
E>Я сделал счетчики ссылок на основе ACE_Atomic_Op. Причем вот с такой оптимизацией: счетчик ссылок на агента изменяется только два раза: E>1) при первом инкременте ссылки на msg_data_impl_t/event_data_impl_t (т.е. когда эти объекты только были созданы); E>2) при последнем декременте ссылки на msg_data_impl_t/event_data_impl_t (т.е. когда эти объекты будут уничтожены).
E>Все остальные инкременты/декременты msg_data_impl_t/event_data_impl_t уже не оказываеют влияния на счетчик ссылок агента.
Я как раз примерно такую схему хотел предложить...
E>Такая оптимизация должна особенно сильно сказаться в случаях, когда msg_data_impl_t тиражируется (большое количество подписчиков на сообщение или сообщение является отложенным/периодическим).
E>Если же ACE для какой-то платформы не предоставляет эффективной реализации ACE_Atomic_Op, то используется старая схема с мутексом.
Да, мне кажется, что на всех она должна быть. Там разьве есть какие-то где нет?
E>
E>Я вот что думаю -- может эксперименты по выжиманию скорости из send_msg() пока прекратить. Для версии 4.4.b4 есть еще одна очень важная задача -- изменения способа работы с транспортными каналами, т.к. текущая схема очень проста и неэффективна. Я думаю сосредоточится сейчас именно на транспортном слое. Это и будет версия 4.4.b4. Если же за это время появятся хорошие, не сложные и жизнеспособные идеи по дальнейшей оптимизации send_msg(), то их можно будет попробовать воплотить в 4.4.b5. E>Ты как думаешь? (Может еще кто-нибудь из читателей форума выскажется?)
Я ещё хотел такой вопрос поднять. А зачем вообще для евентов нужен подсчёт ссылок? Нельзя просто так — при диспетчеризации создали евенты, после обработки просто вызвали delete. Ну на агента всё равно в конструкторе инкрементируем, в деструкторе декрементируем.
Или для этого есть какие-то препядствия, когда более сложные схемы обрабоки?
Или вообще можно под евенты динамически память не выделять — там 16 байт — просто по значению и передавать и в очередь по значению и класть... Но на агента всё равно при этом придётся делать инкремент/декремент.
Из простых оптимизаций, которые не влияют на структуру системы, ещё могу обратить внимание на функцию:
Тут для евента делается инкремент и декремент — и потом при непосредственно кладении в очередь demand_queue_t::push() ещё раз делается инкремент:
// Для защиты от исключений в момент помещения
// объекта в очередь.
std::auto_ptr< so_4::rt::event_data_t > event_data_guard(
new so_4::rt::event_data_t( event ) );
Можно их совместить в один — т.е. сразу в функции send_event_to_dispatcher() создавать объект динамически, класть в std::auto_ptr и потом делать release().
И ещё — для всех объектов в конструкторе устанавливается счётчик в 0, потом явно инкрементируется. Было бы более целесообразно устанавливать в конструкторе счётчик в 1, тогда один инкремент убирается.
И даже больше — пока идёт диспетчиризация, т.е. внутри функции send_msg(), счётчик для msg_data_impl_t можно менять не атомарной операцией, а обычным "бесплатным" инкрементом. Т.е. допустим на сообщение создаётся 10 евентов — у сообщения просто ставим счётчик в 10. А потом, когда евенты рушатся, уже декрементируем счётчик сообщения атомарно. Т.о. тоже можно несколько лишних инкрементов убрать. Особенно, если евентов на сообщение создаётся много.
И ещё есть такой трюк малоизвестный, но прикольный. Он ещё один декремент уберёт.
Допустим у тебя есть код:
if (0 == atomic_dec(obj->ref_count))
delete obj;
Его можно заменить на равноценный:
if (1 == obj->ref_count)
delete obj;
else if (0 == atomic_dec())
delete obj;
При такой модели владения, как здесь используется, такая замена безопасна.
Получается, если этот объект последний держит ссылку и он об этом знает, или он единственный и держал, то атомарный декремент можно не делать, а сразу удалять объект.
Т.о. если это всё воплотить, то получается следующее. Допустим на сообщение создаётся один евент. Инкрементируем счётчик агента сообщения, создаём сообщение сразу со счётчиком 1, создаём евент, инкрементируем счётчик агента евента. Кладём евент в очередь. Евент обрабатывается и удаляется. Декрементируется счётчик агента евента. Декрементируется счётчик сообщения без атомарных операций. Декрементируется счётчик агента сообщения.
Т.е. из атомарных операций всего 1 инкремент/декремент агента сообщения и 1 инкремент/декремент агента евента.
И ещё было бы круто разобраться с системным словарём. Т.к. при отправке сейчас 3 входа в rw_mutex (12 атомарных операций), а можно заменить на 1 вход в rw_mutex (4 атомарных операции). А если сделать крутой rw_mutex с lock-free быстрым путём, то останется только 2 атомарных операции.
Теоретически ещё можно оптимизировать demand_queue_t, т.к. там сейчас при добавлении евента 4 атомарных операции и туда все отправки упираются, т.е. там конкуренция сильная.
А вот кстати, что там можно сейчас сделать быстро — вынести создание евента из под мьютекса:
std::auto_ptr< so_4::rt::event_data_t > event_data_guard(
new so_4::rt::event_data_t( event ) );
Т.к. это увеличивает время нахождения под мьютексом.
Моё видение такое. Я бы сделал примерно там, если бы сейчас делал с нуля. Ну ты сам смотри. Я так чувствую, что становлюсь немного навязчивым
Здравствуйте, remark, Вы писали:
E>>Если же ACE для какой-то платформы не предоставляет эффективной реализации ACE_Atomic_Op, то используется старая схема с мутексом.
R>Да, мне кажется, что на всех она должна быть. Там разьве есть какие-то где нет?
Если заглянуть в Atomic_Op.h, то там можно увидеть, что ACE_HAS_BUILTIN_ATOMIC_OP определяется для совсем небольшого количества платформ.
Например, нам приходилось работать на такой платформе, как HP NonStop. Ее в Atomic_Op.h нет, но поскольку сама платформа из-за собственных особенностей не сильно быстрая, то не хочется на ней терять производительность из-за того, что ACE_Atomic_Op будет работать через Mutex.
R>Я ещё хотел такой вопрос поднять. А зачем вообще для евентов нужен подсчёт ссылок? Нельзя просто так — при диспетчеризации создали евенты, после обработки просто вызвали delete. Ну на агента всё равно в конструкторе инкрементируем, в деструкторе декрементируем. R>Или для этого есть какие-то препядствия, когда более сложные схемы обрабоки?
R>Или вообще можно под евенты динамически память не выделять — там 16 байт — просто по значению и передавать и в очередь по значению и класть... Но на агента всё равно при этом придётся делать инкремент/декремент.
Вообще-то идея была в том, чтобы предоставить пользователям самим определять собственные диспетчеры под собственные нужды без знания деталей реализации SObjectizer-а. Как в этих диспетчерах будет использоваться event_data_t сложно предположить -- он может либо быть сразу использован при вызове dispatcher_t::dispatch(), либо же пройдет долгий путь по внутренним структурам прежде чем попадет на обработку (как в случае с диспетчером для GUI библиотек, таких как Qt). Для облегчения жизни писателей диспетчера и была сделана схема, в которой event_data_t является аналогом shared_ptr с подсчетом ссылок для event_data_impl_t (и производных от event_data_impl_t сущностей). С ее помощью у разработчиков диспетчеров может быть больше свободы -- например, не задумываясь о последствиях можно помещать event_data_t в различные типы стандартных контейнеров.
Вот от выделения памяти под event_data_t, вероятно, стоит отказаться. Спасибо!
R>И ещё — для всех объектов в конструкторе устанавливается счётчик в 0, потом явно инкрементируется. Было бы более целесообразно устанавливать в конструкторе счётчик в 1, тогда один инкремент убирается. R>И даже больше — пока идёт диспетчиризация, т.е. внутри функции send_msg(), счётчик для msg_data_impl_t можно менять не атомарной операцией, а обычным "бесплатным" инкрементом. Т.е. допустим на сообщение создаётся 10 евентов — у сообщения просто ставим счётчик в 10. А потом, когда евенты рушатся, уже декрементируем счётчик сообщения атомарно. Т.о. тоже можно несколько лишних инкрементов убрать. Особенно, если евентов на сообщение создаётся много.
Не совсем так. В send_msg() есть момент, когда при разблокированном ядре msg_data_t отдается таймерной нити. Таймерная нить работает в параллель и может манипулировать msg_data_t в тоже самое время, пока send_msg() вызывает deliver_msg_on_nonblocked_kernel.
R>И ещё есть такой трюк малоизвестный, но прикольный. Он ещё один декремент уберёт. R>Допустим у тебя есть код: R>
R>При такой модели владения, как здесь используется, такая замена безопасна. R>Получается, если этот объект последний держит ссылку и он об этом знает, или он единственный и держал, то атомарный декремент можно не делать, а сразу удалять объект.
Как-то пока мне это кажется подозрительным. Я покурю эту тему еще.
За остальные твои предложения отдельное спасибо! Мне нужно обдумать их в спокойной обстановке.
R>Моё видение такое. Я бы сделал примерно там, если бы сейчас делал с нуля. Ну ты сам смотри. Я так чувствую, что становлюсь немного навязчивым
Отнюдь. Именно для этого мы и открывали SObjectizer, чтобы попробовать привлечь таких вьедливых энтузиастов, как ты. Я уже слишком давно занимаюсь SObjectizer-ом и, как ты сам смог убедиться, уже не замечаю многих вещей. Только такие свежие люди, как ты, способны наполнить SObjectizer "свежей кровью".
Так что я могу только попросить тебя продолжать в том же духе. И не обижаться, если по каким-то причинам я задерживаюсь с ответами на твои очень интересные предложения.
R>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>>>Если же ACE для какой-то платформы не предоставляет эффективной реализации ACE_Atomic_Op, то используется старая схема с мутексом.
R>>Да, мне кажется, что на всех она должна быть. Там разьве есть какие-то где нет?
E>Если заглянуть в Atomic_Op.h, то там можно увидеть, что ACE_HAS_BUILTIN_ATOMIC_OP определяется для совсем небольшого количества платформ.
Ну это видимо только из-за лени Шмидта
E>Например, нам приходилось работать на такой платформе, как HP NonStop. Ее в Atomic_Op.h нет, но поскольку сама платформа из-за собственных особенностей не сильно быстрая, то не хочется на ней терять производительность из-за того, что ACE_Atomic_Op будет работать через Mutex.
NonStop Kernel работал на MIPS — там есть инструкции LL/SC, что является аналогом CAS, а с MIPS II там даже есть LLD/SCD для атомарной работы с двойными словами. А сейчас NonStop делают на Itanium — там с этим точно всё в порядке — набор команд похож на x86.
Ну другое дело, конечно, что если в ACE нет, то придётся это руками докручивать... но для поддержки такой экзотической платформы как NonStop, наверное можно немного и докрутить...
Здравствуйте, eao197, Вы писали:
R>>И ещё — для всех объектов в конструкторе устанавливается счётчик в 0, потом явно инкрементируется. Было бы более целесообразно устанавливать в конструкторе счётчик в 1, тогда один инкремент убирается. R>>И даже больше — пока идёт диспетчиризация, т.е. внутри функции send_msg(), счётчик для msg_data_impl_t можно менять не атомарной операцией, а обычным "бесплатным" инкрементом. Т.е. допустим на сообщение создаётся 10 евентов — у сообщения просто ставим счётчик в 10. А потом, когда евенты рушатся, уже декрементируем счётчик сообщения атомарно. Т.о. тоже можно несколько лишних инкрементов убрать. Особенно, если евентов на сообщение создаётся много.
E>Не совсем так. В send_msg() есть момент, когда при разблокированном ядре msg_data_t отдается таймерной нити. Таймерная нить работает в параллель и может манипулировать msg_data_t в тоже самое время, пока send_msg() вызывает deliver_msg_on_nonblocked_kernel.
Я подозревал, что что-то такое есть
Ну может можно вначале отдеспетчеризировать в send_msg(), а потом уже отдавать в поток таймера... ну или ещё как-то так...