четверг, 3 июля 2014 г.

[prog.c++] О принципиальных различиях между SObjectizer и libcppa

В продолжение вчерашней темы про libcppa. Борис Сивко подсказал ссылку на хороший PDF-документик, посвященный основам libcppa и сравнению производительности этой библиотеки с Erlang, Scala Actors и Akka. Так что если у кого-то есть желание узнать, что такое libcppa, то можно начинать с этой PDF-ки, там всего 10 страниц и общее впечатление она дает не хуже, чем полноценный большой мануал.

Что же до сравнения SObjectizer-5 и libcppa, то его, собственно говоря, можно начинать и заканчивать на одной простой теме. libcppa заточен на то, что в приложении будет работать всего один экземпляр рантайма libcppa. Посему все выглядит так, как выглядит -- вся эта похожесть на Erlang и простое использование spawn, await_all_actors_done, shutdown, self -- все это из-за того, что любой агент, созданный в приложении будет принадлежать единственному запущенному в приложении рантайму libcppa. Второго параллельно запущенного здесь же рантайма не может быть в принципе.

Это те же самые грабли, которые я сделал создавая SObjectizer-4. И на которые наступил, когда для заказчиков пришлось написать парочку библиотек, выставляющих наружу простой API, а внутри использующих SObjectizer и запускающих SObjectizer RunTime. Все это было хорошо ровно до тех пор, пока одну из библиотек нам самим не пришлось использовать в собственном SObjectizer-приложении. Получилась коллизия: приложение стартует и запускает свой SObjectizer RunTime, затем подгружает DLL с кодом, использующим библиотеку, библиотека пытается запустить SObjectizer RunTime еще раз. Пришлось делать костыльное решение. Костыльное потому, что для нормального решения требовалось, чтобы можно было параллельно и независимо друг от друга запускать несколько SObjectizer-4 RunTime. А это было невозможно из-за прибитого гвоздями к единственной копии RunTime прикладному API SObjectizer-4.

Эта проблема была в числе тех, которые мы старались разрешить в SObjectizer-5. Поэтому у нас появилось понятие SObjectizer Environment (это контейнер, в котором находится отдельный экземпляр SObjectizer RunTime) и интерфейс so_environment_t. А так же необходимость связывать агентов с данным интерфейсом и проводить ряд операций (например, создание и регистрация коопераций, создание mbox-ов, останов RunTime) через so_environment_t. Что не могло не сказаться на многословности определения агентов в SObjectizer и особенностями внешнего вида использующего SObjectizer-5 кода.

Зато SObjectizer-5 позволяет, например, написать библиотеку для вставки водяных знаков в видео. В этой библиотеке SObjectizer может использоваться для распараллеливания работы на несколько ядер. И, скажем, библиотеку, которая внутри себя задействует SObjectizer для работы с аппаратно реализованной криптографией (HSM). А затем задействовать обе эти библиотеки в одном приложении, где с помощью второй библиотеки формируются криптографически стойкие подписи, а затем средствами второй библиотеки эти подписи вставляются в виде водяных знаков в видеофайл. И ни одна из библиотек не будет подозревать, что рядом запущен еще один SObjectizer RunTime.

С libcppa так не получится.

Но есть и еще одно различие, менее формальное, но не менее важное. Хоть и чисто субъективное (ну вот так мне кажется и ничего с этим не поделать).

На мой взгляд, SObjectizer -- это фреймворк для coarse-grained actors, тогда как libcppa -- это фреймворк для fine-grained actors (для прояснения различий между coarse- и fine-grained прошу в Wikipedia). Т.е. SObjectizer разрабатывался с прицелом на работу с "тяжеловесными" агентами, такими, например, как агент для обслуживания SMPP канала. Такой агент может иметь несколько десятков обработчиков сообщений, получать десяток-другой разных типов сообщений, требовать работы на контексте выделенной нити и т.д. Тогда как libcppa в большей степени ориентирован на очень эффективное обслуживание совсем мелких агентов, подавляющее большинство из которых вполне могут работать на одной общей нити. Отсюда в документации по libcppa и соответствующие примеры. Скажем, пример fixed_stack, в котором агент реализует стек целых чисел фиксированного размера. Я себе даже представить не могу реализацию такого агента на SObjectizer -- это чистой воды overkill, слишком тяжеловесной выйдет реализация (если не по объему кода, то по затрате ресурсов при работе). С другой стороны, я вспоминаю некоторые объемные агенты, написанный мной за время работы в Интервэйле, и слабо себе представляю, как мелко их пришлось бы "нарезать", чтобы реализовать в libcppa...

Вот такие пока предварительные выводы. Двигаюсь дальше. Если еще что-то придет в голову, вернусь к данной теме.

Под катом сравнение реализации примера fixed_stack из документации к libcppa и его аналога на SObjectizer (предупреждение: ни один из примеров не проверялся компилятором/линкером).

libcppa SObjectizer-5
class fixed_stack : public sb_actor<fixed_stack>
{
   friend class sb_actor<fixed_stack>;

   size_t max_size;
   vector<int> data;
   
   behavior full;
   behavior filled;
   behavior empty;
   
   behavior& init_state = empty;
public:
   fixed_stack(size_t max) : max_size(max)
   {
      full = (
         on(atom("push"), arg_match) >> [=](int) { /* discard */ },
         on(atom("pop")) >> [=]() -> cow_tuple<atom_value, int> {
            auto result = data.back();
            data.pop_back();
            become(filled);
            return {atom("ok"), result};
         } );
      filled = (
         on(atom("push"), arg_match) >> [=](int what) {
            data.push_back(what);
            if (data.size() == max_size) become(full);
         },
         on(atom("pop")) >> [=]() -> cow_tuple<atom_value, int> {
            auto result = data.back();
            data.pop_back();
            if (data.empty()) become(empty);
            return {atom("ok"), result};
         } );
      empty = (
         on(atom("push"), arg_match) >> [=](int what) {
            data.push_back(what);
            become(filled);
         },
         on(atom("pop")) >> [=] {
            return atom("failure");
         } );
   }
};
class fixed_stack : public so_5::rt::agent_t
{
  const so_5::rt::mbox_ref_t m_mbox;
  so_5::rt::state_t st_empty;
  so_5::rt::state_t st_filled;
  so_5::rt::state_t st_full;
 
  const size_t m_max_size;
  std::vector< int > m_stack;
 
public :
  fixed_stack( so_5::rt::environment_t & env,
    const so_5::rt::mbox_ref_t & mbox,
    size_t max_size )
    : so_5::rt::agent_t( env )
    , m_mbox( mbox )
    , st_empty( so_self_ptr(), "empty" )
    , st_filled( so_self_ptr(), "filled" )
    , st_full( so_self_ptr(), "full" )
    , m_max_size( max_size )
    {}
 
  struct msg_push : public so_5::rt::message_t
  {
    int m_val;
  };
  struct msg_pop : public so_5::rt::signal_t {};
 
  virtual void
  so_define_agent()
  {
    so_change_state( st_empty );
 
    so_subscribe( m_mbox ).in( st_empty ).in( st_filled ).event(
    [this]( const msg_push & w ) {
      m_stack.push_back( w.m_val );
      if( m_stack.size() == m_max_size )
        so_change_state( st_full );
    } );
 
    so_subscribe( m_mbox ).in( st_filled ).in( st_full ).event( so_5::signal< msg_pop >,
    [this]() {
      auto r = m_stack.back();
      m_stack.pop_back();
      so_change_state( m_stack.empty() ? st_empty : st_filled );
      return r;
    } );
 
    so_subscribe( m_mbox ).in( st_empty ).event( so_5::signal< msg_pop >,
    []() {
      throw std::runtime_error( "empty_stack" );
    } );
  }
};

Комментариев нет: