Пары значений ни при каком раскладе к динамически создаваемому объекту не приведут.
Я этого и не утверждал. Только вот тот, кто вызывает метод f() не может знать, положит ли метод f() в результат простой Err или же это будет созданный динамически объект, который реализует нужный трайт.
Дропы тоже будут использоваться только там где они реально нужны.
Наличие паник подразумевает, что должен быть какой-то механизм автоматического раскручивания Drop-ов при выбросе паники по аналогии с плюсовыми деструкторами и исключениями. Это не бесплатно, даже если паники не бросаются.
Если уж сравнивать числодробительные возможности, то тогда надо смотреть на бенчмарки.
Речь шла про общий случай, а не про числодробилки.
rust в общем случае не должен уступать по скорости C
Это почему это? В Rust используется bound checking при обращении к массивам по индексам, тогда как в C — нет. В ряде случаев Rust-овый компилятор способен избавиться от таких проверок (например, при итерациях), но не всегда.
Плюс в Rust-е есть Drop-ы, которые аналоги плюсовых деструкторов, и Drop-ы должны вызываться при выходе из скоупа. Что так же не бесплатно.
Плюс в Rust-е практикуется возврат Result-ов, т.е. пар значений. И в Result-е запросто может оказаться динамически созданный объект на месте Err. Что так же не дешево.
Плюс в Rust-е иногда может применяться динамический диспатчинг вызовов методов трайтов, косвенный вызов дороже прямого.
Скорее в общем случае Rust должен хоть немного, но отставать от C.
На эту тему было много разговоров в Интернетах (например). Конкретно разыменование нулевого указателя для получения ссылки — это UB. Поскольку результирующая ссылка будет содержать не пойми что, то и операция взятия адреса от нее так же может давать не пойми что. Пока это все работает (т.к., по сути ссылка и указатель на низком уровне это одно и то же), но когда оптимизаторы в компиляторах станут еще умнее, то всякое может быть.
Статическая переменная внутри inline-функции в header-only библиотеках, емнип, стабильно работает пока речь идет о статической линковке всего кода в один исполняемый файл. Если же мы начинаем работать с dll/so, то в каждой из них может оказаться свой экземпляр статической переменной.
Разыменовать нулевой указатель дабы получить гарантированно несуществующую ссылку, а потом взять адрес от этой ссылки и сравнить его с чем-то… UB попахивает и не удивлюсь, если в каком-то из компиляторов такое не сработает рано или поздно. Может безопаснее было сделать обертку вокруг std::error_code*?
Никогда не доводилось применять MPI, так что у меня только поверхносные впечатления об этом инструменте. Но простые очереди сообщений использовать приходилось.
На мой взгляд, Модель Акторов — это следующий логический шаг в степени использования message-passing. Т.е. сперва вы решаете, что вам нужны независимые потоки управления, у каждого из которых свои собственные данные. И для общения между потоками вам нужен обмен сообщениями.
Затем вы понимаете, что у ваших потоков появляется какое-то сложное поведение, которое зависит от того, какие сообщения поток получает. Потом вы обнаруживаете, что можно как бы разделить логические потоки и физические. И что логических потоков вам нужно больше, чем физических. Вам теперь нужно отобразить N логических потоков на M физических.
И вот эти самые логические потоки оказываются вполне себе акторами. Которые могут выглядеть по разному. Как об'екты или как сопрограммы. Просто к вам в помощь появляется еще и некоторый шедулер, который акторами управляет.
Ну и еще замечу, что MPI предназначен для решения проблем parallel computing, тогда как акторы — для concurrent computing. Специфика немного разная.
С таким же успехом можно сказать «вызов функций в принципе ненадёжен»…
Между синхронным вызовом и асинхронной отсылкой сообщения есть принципиальная разница. В случае вызова для вызывающего кода нет ничего между вызовом и получением результата (даже если результат — это исключение о невозможности выполнить вызов).
В случае отсылки сообщения отправитель продолжает нормально работать пока сообщение дойдет до получателя. Дойдет или нет — никто никаких гарантий не дает. Есть ряд причин, по которым доставка и обработка отосланного сообщения может не состоятся.
Надёжность определяется средой передачи и протоколом.
Странная фраза. Могли вы пояснить, какое отношение она имеет к обмену сообщениями между акторами внутри одного процесса (хотя бы одного процесса для простоты)?
Нужно это примерно для того же, для чего разработчики protobuf создали тип Any.
Для чего нужно нечто, что можно охарактеризовать как opaque payload, в транспортных протоколах — понятно. Зачем акторам иметь возможность получить сообщение, типа которого они не знают — нет. Можно прикладной пример?
слишком мутное описание, чтобы из него можно было что-то почерпнуть об алгоритме.
Так описания алгоритма и не было. Речь о том, что когда идет интенсивный поток заявок, а обработка каждой заявки занимает очень небольшое время, то на практике очень хорошо зарекомендовал себя подход Disruptor.
Есть ли в SObjectizer статическая типизация агентов? Как это сделано у Вас?
Не понятно, что подразумевается под статической типизацией. Посмотрите пример из статьи. Метод fixed_stack::on_push будет вызван только для сообщения push, ни для какого другого сообщения его использовать не получится. Возможно это как раз посредством статической типизации.
Вероятно, вы под статической типизацией понимаете что-то другое. Например, невозможность отправить агенту сообщение типа A, если агент в нем не заинтересован. Если так, то этого в SObjectizer нет. Поскольку на практике с такими задачами не встречались. Где это может потребоваться?
Подразумевается, что актор может быть как функцией, так и классом. К чему требовать классы там, где достаточно лябды?
Прекрасно, нет проблем. Можно так:
class onliner_demo final : public so_5::agent_t {
public :
onliner_demo(context_t ctx) : so_5::agent_t(std::move(ctx)) {
so_subscribe_self()
.event( &onliner_demo::event_A )
.event( &onliner_demo::event_B );
}
private :
void event_A(const A &) {...}
void event_B(const B &) {...}
};
Можно так:
auto coop = env.create_coop("demo");
auto onliner_demo = coop.define_agent();
onliner_demo.event( onliner_demo, [](const A &) {...});
onliner_demo.event( onliner_demo, [](const A &) {...});
Причем для самого SO-5 разницы между этими агентами никакой нет, поскольку во втором случае неявным образом конструируется обычный наследник agent_t.
Соответственно, вне зависимости от вида оформления агента ему доступны все, что доступно агенту (подписки, привязка к диспетчерам и т.д.).
не всегда возможно запихнуть всё приложение в модель акторов (gui, например), поэтому необходим тип акторов, который живёт «вовне» (scoped_actor) и управляется внешним event-loop
Тут непонятно зачем такие сущности обзывать акторами и делать для них какие-то специальные варианты. Либо есть актор, который привязывается к соответствующему диспетчеру (а диспетчер можно сделать так, чтобы он работал на GUI-нити или на другой нити со своим event-loop-ом). Либо это вообще не актор, а просто внешняя сущность, которой нужно тем или иным образом с акторами взаимодействовать. Опять же, это решается без выделения особых типов акторов.
Для работы внутри системы акторов также вохможны 2 ситуации:
— работа, основанная на сообщениях от других акторов (event_based_actor)
— работа, требующая ручного управления событиями внутри актора (сокеты, железо, бд и тп) (blocking_actor)
Это только кажется, что эти ситуации различаются. Различается вот что: нужен ли актору эксклюзивный рабочий контекст (дабы актор ни с кем больше его не делил и мог выполнять на этом контексте сколь угодно долгие операции), либо же актор может делить контекст с другими акторами.
В SObjectizer контекстами управляет диспетчер, к какому агента привяжут, так он и будет работать. Нужен агенту эксклюзивный контекст — он привязывается либо к active_obj-диспетчеру, либо к собственному one_thread-диспетчеру. При этом внешний вид актора и его внутреннее поведение никак не меняется.
В SObjectizer настолько прост, что в нём ненужно разбираться?
Я вам больше скажу :)
Ключевым фактором является «тяжесть» обработки сообщения. Чем короче время обработки, тем меньше выгод в разнесении обработчиков по разным потокам. Даже при высокой интенсивности потока сообщений.
При очень высокой интенсивности и совсем дешевой обработке, вообще актуальность использования Модели Акторов под вопросом. Тут может лучше работать подход на основе Disruptor-а.
Что касается управления контекстами, то сейчас в SObjectizer восемь типов готовых диспетчеров и разработчик может распределять своих акторов между ними как ему захочется, в том числе выбирая конфигурацию прямо в run-time. Чему способствует так же и то, что в SObjectizer всего один вид агентов, а не зоопарк, как в CAF :)
Ну и на счет зоопарка в CAF. Документация говорит, что там есть где развернуться:
CAF provides several actor implementations, each covering a particular use case. The available implementations differ in three characteristics: (1) dynamically or statically typed, (2) class-based or function-based, and (3) using asynchronous event handlers or blocking receives.
И, скажем, Pub/Sub на базе CAF-овских групп доступен для dynamically typed акторов, но не для statically typed.
Вероятно, кому-то доставляет удовольствие со всем этим разбираться :)
Не акторы, а акторный фреймворк. Как раз это один из факторов, благодаря которому разработка многопоточного софта с использованием акторных фреймворков оказывается проще, чем на «голых» нитях/мутексах. Программист берет готовый, отлаженный и опробированный в разных проектах инструмент и получает работающие mpsc/mpmc очереди «из коробки». Ему не нужно очереди программировать вручную, отлаживать их и т.д.
Причем этот бонус разработчик получает не только в случае использования акторных фреймворков, но и в случае использования фреймворков с реализации других подходов к решению проблем concurrent computing: будь то реализация модели Pub/Sub или CSP. Но акторные фреймворки, особенно те, в которых актор — это не поток ОС, а объект или короутина, дают еще одно преимущество: они берут на себя задачу диспетчеризации событий для акторов. Тем самым снимая с разработчика еще и заботы об организации рабочих потоков, распределение задач по ним и т.д.
> Текст претендует на некоторое сравнение существующих фреймфорков
Вам показалось, не претендует.
> Но при этом не содержит упоминания о реализуемых моделях в других реализациях.
Про механизм групп в CAF у меня вообще не было представления, мне казалось, они там вообще для каких-то других целей использовались. Может быть это какое-то свежее добавление в 0.15.
CAF-ом никогда не пользовался, приходилось на него смотреть изредка, когда вот с подобными комментариями сталкивался. Что знал, рассказал. У кого есть опыт, пожалуйста, делитесь. Наверняка получится интереснее и подробнее, чем у меня.
Было бы хорошо, если бы тот, кто имеет положительный опыт использования CAF-а, рассказал бы об этом фреймворке русскоязычным читателям в отдельной статье.
Ну и где речь идет об «игнорировании платформы»? Значимой платформой Windows для них не была, они сами об этом, по-моему, в своем блоге писали. Мол, чем тратить время на борьбу с заморочками msvs, мы лучше сделаем что нам нужно на самых свежих версиях gcc и clang.
Что до 2017-го года, то, к сожалению, еще есть проекты, которые пока еще и не думают на vc 14.0 переходить, хорошо, если хотя бы на vc 12.0 сидят, а то могут и на более старых компиляторах оставаться.
В общем, если вам нравится CAF, то уж простите, что я высказался в их адрес столь жестко. Но я видел развитие событий именно таким образом.
Да куда уж мне.
А вы подумайте, как эти частности скажутся в сумме.
У табличного способа поиска исключений есть своя цена, даже если исключения не бросаются. Хотя бы в необходимости хранения этих самых таблиц.
И какой тип получается вот в этом примере из стандартной документации? Неужели Box<Error>?
Это будет не общий случай.
Это подразумевается по умолчанию. Если намеренно игнорировать безопасность Rust-а, смысла в его использовании нет.
Абстракции в C++ далеко не нулевой стоимости.
Я этого и не утверждал. Только вот тот, кто вызывает метод f() не может знать, положит ли метод f() в результат простой Err или же это будет созданный динамически объект, который реализует нужный трайт.
Наличие паник подразумевает, что должен быть какой-то механизм автоматического раскручивания Drop-ов при выбросе паники по аналогии с плюсовыми деструкторами и исключениями. Это не бесплатно, даже если паники не бросаются.
Речь шла про общий случай, а не про числодробилки.
Это почему это? В Rust используется bound checking при обращении к массивам по индексам, тогда как в C — нет. В ряде случаев Rust-овый компилятор способен избавиться от таких проверок (например, при итерациях), но не всегда.
Плюс в Rust-е есть Drop-ы, которые аналоги плюсовых деструкторов, и Drop-ы должны вызываться при выходе из скоупа. Что так же не бесплатно.
Плюс в Rust-е практикуется возврат Result-ов, т.е. пар значений. И в Result-е запросто может оказаться динамически созданный объект на месте Err. Что так же не дешево.
Плюс в Rust-е иногда может применяться динамический диспатчинг вызовов методов трайтов, косвенный вызов дороже прямого.
Скорее в общем случае Rust должен хоть немного, но отставать от C.
Статическая переменная внутри inline-функции в header-only библиотеках, емнип, стабильно работает пока речь идет о статической линковке всего кода в один исполняемый файл. Если же мы начинаем работать с dll/so, то в каждой из них может оказаться свой экземпляр статической переменной.
На мой взгляд, Модель Акторов — это следующий логический шаг в степени использования message-passing. Т.е. сперва вы решаете, что вам нужны независимые потоки управления, у каждого из которых свои собственные данные. И для общения между потоками вам нужен обмен сообщениями.
Затем вы понимаете, что у ваших потоков появляется какое-то сложное поведение, которое зависит от того, какие сообщения поток получает. Потом вы обнаруживаете, что можно как бы разделить логические потоки и физические. И что логических потоков вам нужно больше, чем физических. Вам теперь нужно отобразить N логических потоков на M физических.
И вот эти самые логические потоки оказываются вполне себе акторами. Которые могут выглядеть по разному. Как об'екты или как сопрограммы. Просто к вам в помощь появляется еще и некоторый шедулер, который акторами управляет.
Ну и еще замечу, что MPI предназначен для решения проблем parallel computing, тогда как акторы — для concurrent computing. Специфика немного разная.
Между синхронным вызовом и асинхронной отсылкой сообщения есть принципиальная разница. В случае вызова для вызывающего кода нет ничего между вызовом и получением результата (даже если результат — это исключение о невозможности выполнить вызов).
В случае отсылки сообщения отправитель продолжает нормально работать пока сообщение дойдет до получателя. Дойдет или нет — никто никаких гарантий не дает. Есть ряд причин, по которым доставка и обработка отосланного сообщения может не состоятся.
Странная фраза. Могли вы пояснить, какое отношение она имеет к обмену сообщениями между акторами внутри одного процесса (хотя бы одного процесса для простоты)?
Для чего нужно нечто, что можно охарактеризовать как opaque payload, в транспортных протоколах — понятно. Зачем акторам иметь возможность получить сообщение, типа которого они не знают — нет. Можно прикладной пример?
Оно будет просто проигнорировано.
Доставка сообщений вообще в принципе ненадежна. Так что заруливание «не туда» или потеря по какой-то другой причине — особой разницы нет.
Не понятно, зачем это нужно. Но, раз сделали, значит нужно зачем-то.
Так описания алгоритма и не было. Речь о том, что когда идет интенсивный поток заявок, а обработка каждой заявки занимает очень небольшое время, то на практике очень хорошо зарекомендовал себя подход Disruptor.
Не понятно, что подразумевается под статической типизацией. Посмотрите пример из статьи. Метод fixed_stack::on_push будет вызван только для сообщения push, ни для какого другого сообщения его использовать не получится. Возможно это как раз посредством статической типизации.
Вероятно, вы под статической типизацией понимаете что-то другое. Например, невозможность отправить агенту сообщение типа A, если агент в нем не заинтересован. Если так, то этого в SObjectizer нет. Поскольку на практике с такими задачами не встречались. Где это может потребоваться?
Прекрасно, нет проблем. Можно так:
Можно так:
Причем для самого SO-5 разницы между этими агентами никакой нет, поскольку во втором случае неявным образом конструируется обычный наследник agent_t.
Соответственно, вне зависимости от вида оформления агента ему доступны все, что доступно агенту (подписки, привязка к диспетчерам и т.д.).
Тут непонятно зачем такие сущности обзывать акторами и делать для них какие-то специальные варианты. Либо есть актор, который привязывается к соответствующему диспетчеру (а диспетчер можно сделать так, чтобы он работал на GUI-нити или на другой нити со своим event-loop-ом). Либо это вообще не актор, а просто внешняя сущность, которой нужно тем или иным образом с акторами взаимодействовать. Опять же, это решается без выделения особых типов акторов.
Это только кажется, что эти ситуации различаются. Различается вот что: нужен ли актору эксклюзивный рабочий контекст (дабы актор ни с кем больше его не делил и мог выполнять на этом контексте сколь угодно долгие операции), либо же актор может делить контекст с другими акторами.
В SObjectizer контекстами управляет диспетчер, к какому агента привяжут, так он и будет работать. Нужен агенту эксклюзивный контекст — он привязывается либо к active_obj-диспетчеру, либо к собственному one_thread-диспетчеру. При этом внешний вид актора и его внутреннее поведение никак не меняется.
Люди говорят, что проще, чем CAF.
Ключевым фактором является «тяжесть» обработки сообщения. Чем короче время обработки, тем меньше выгод в разнесении обработчиков по разным потокам. Даже при высокой интенсивности потока сообщений.
При очень высокой интенсивности и совсем дешевой обработке, вообще актуальность использования Модели Акторов под вопросом. Тут может лучше работать подход на основе Disruptor-а.
Что касается управления контекстами, то сейчас в SObjectizer восемь типов готовых диспетчеров и разработчик может распределять своих акторов между ними как ему захочется, в том числе выбирая конфигурацию прямо в run-time. Чему способствует так же и то, что в SObjectizer всего один вид агентов, а не зоопарк, как в CAF :)
Ну и на счет зоопарка в CAF. Документация говорит, что там есть где развернуться:
И, скажем, Pub/Sub на базе CAF-овских групп доступен для dynamically typed акторов, но не для statically typed.
Вероятно, кому-то доставляет удовольствие со всем этим разбираться :)
Причем этот бонус разработчик получает не только в случае использования акторных фреймворков, но и в случае использования фреймворков с реализации других подходов к решению проблем concurrent computing: будь то реализация модели Pub/Sub или CSP. Но акторные фреймворки, особенно те, в которых актор — это не поток ОС, а объект или короутина, дают еще одно преимущество: они берут на себя задачу диспетчеризации событий для акторов. Тем самым снимая с разработчика еще и заботы об организации рабочих потоков, распределение задач по ним и т.д.
Вам показалось, не претендует.
> Но при этом не содержит упоминания о реализуемых моделях в других реализациях.
Про механизм групп в CAF у меня вообще не было представления, мне казалось, они там вообще для каких-то других целей использовались. Может быть это какое-то свежее добавление в 0.15.
CAF-ом никогда не пользовался, приходилось на него смотреть изредка, когда вот с подобными комментариями сталкивался. Что знал, рассказал. У кого есть опыт, пожалуйста, делитесь. Наверняка получится интереснее и подробнее, чем у меня.
Боюсь, там это будет все-таки оффтопик.
Но вот в какой блог нужно было разместить данную статью я так и не понял.
Что до 2017-го года, то, к сожалению, еще есть проекты, которые пока еще и не думают на vc 14.0 переходить, хорошо, если хотя бы на vc 12.0 сидят, а то могут и на более старых компиляторах оставаться.
В общем, если вам нравится CAF, то уж простите, что я высказался в их адрес столь жестко. Но я видел развитие событий именно таким образом.