пятница, 10 октября 2014 г.

[life.photo] Полный кадр, говорите?

Ну и для разнообразия немного на тему фотографии. Отлично поднимающий настроение ролик. На 13 минут, на английском, но с русскими субтитрами. Смотрится на одном дыхании:

Кстати говоря, шутки шутками, но несколько месяцев назад, после появления у Fuji камер X-T1, X-E2 и объектива 56mm/1.2R я всерьез задумался о том, чтобы перейти на эту систему. Логика была простой: подавляющее большинство снимков в последнее время я делаю только на 85mm-вый фикс. Так что X-E2 с 56mm/1.2 дала бы мне тот же самый результат, да и еще, полагаю, с более лучшим качеством на высоких ISO, чем у старенького D700. Но прикинув, во что обойдется распродажа парка оптики... Да еще и с учетом того, что в РБ у проверенных временем поставщиков цены все-таки заметно выше, чем на штатовском B&H PhotoVideo, оказался от этой затеи. Так что пока продолжаю оставаться в рядах Nikon-оидов. Фулфреймовых, если это можно так назвать :)

[management] Три из 14-ти пунктов для менеджмента от Эдвардса Деминга

Читая Адизеса и Минцберга я не переставал удивляться, насколько в разрез со здравым смыслом и реальной обстановкой "на местах" действовали менеджеры, под руководством которых мне довелось поработать последние два года. Теперь дошел до книги Эдвардса Деминга "Выход из кризиса. Новая парадигма управления людьми, системами и процессами" и картинка повторяется. Вот три пункта из списка в 14-ть пунктов, которые Деминг адресовал американскому менеджменту:

10. Откажитесь от лозунгов, призывов и установления целей для рабочих, требующих "нуля дефектов" и выхода на новый уровень производительности. Такие призывы лишь вызывают враждебность, поскольку в большинстве случаев низкие качество и производительность на совести системы и, следовательно, не подвластны рабочим.

11.a) Устраните количественные нормы и задания для рабочих в цехе. Замените их лидерством.

   b) Исключите управление по целям. Перестаньте управлять по числам и количественным результатам. Замените его лидерством.

12.a) Разрушайте барьеры, которые лишают постоянных рабочих права гордиться своим мастерством. Мастера должны быть ответственны на за числовые показатели, а за качество.

   b) Ломайте барьеры, которые лишают менеджеров и инженеров их права гордится своим мастерством. Это значит, между прочим, отказ от ежегодных аттестаций или рейтингов и от управления по целям.

Имхо, содержимое этих пунктов от Деминга очень перекликается с тем, что я писал про KPI для программистов.

Пагубность нарушения этих пунктов (хотя я про них до сего дня даже не слышал, но ведь это же здравый смысл в чистом виде) объяснялась руководству неоднократно, и на пальцах, и с примерами из прошлого. Да куда там. Зачем столичным управленцам с корочками MBA слушать какого-то провинциала, у которого за плечами всего лишь 20 лет опыта разработки ПО?..

четверг, 9 октября 2014 г.

[prog.flame] Очередной CodeSize Battle: CAF/libcppa vs SObjectizer-5.5.1

Продолжу относительно бесполезное занятие, а именно -- сравнивание похожего кода по такому архиважному критерию, как длина :)

На этот раз в сравнении принимают участие C++ Agent Framework (он же CAF, он же libcaf, он же libcppa) и находящаяся в разработке версия 5.5.1 SObjectizer-а. В качестве примера взят код dining_philosophers из CAF-а и реализовано что-то похожее на SO-5.5.1.

Для начала пару слов о том, в чем суть. Этот пример иллюстрирует проблему обедающих философов. Каждый философ -- это агент, который какое-то время думает, потом становится голодным и пытается взять левую и правую вилки, чтобы поесть. Когда ему это удается, он ест, после чего кладет вилки обратно, а сам возвращается к процессу думания. В классической постановке задачи философ сначала должен брать левую от себя вилку, только затем правую. Но в примере из CAF-а делается "оптимизация" -- философ пытается схватить две сразу. Вероятно, этот вариант был выбран специально, для того, чтобы показать переходы между состояниями агента в условиях, когда порядок получения ответов недетерминирован.

Поясню. Каждая вилка -- это агент. Этому агенту отсылается сообщение-запрос, а он в ответ отсылает свое сообщение. Когда агент-философ берет вилки по очереди, то он знает, что сейчас ожидается ответ от левой вилки, а затем будет ожидаться ответ от правой вилки. Но когда агент-философ одновременно отсылает запросы обеим вилкам сразу, он не знает, от которой из них ответ придет первым. Поэтому получив ответ сперва от правой вилки, агент должен начать ждать ответ от левой и наоборот.

Еще раз отмечу, что данный пример является иллюстрацией проблемы, а не ее решением. Здесь обеспечивается отсутствие тупиков -- если философ не смог взять вторую вилку, то он кладет первую обратно на стол, тем самым позволяя своему соседу взять ее. Но философам не гарантируется, что они когда-нибудь смогут поесть: какой-нибудь бедолага может просто подходить к столу, брать всего лишь одну вилку и, видя, что вторая занята, уходить голодным. Так что это не решение, но просто демонстрация привязки событий к разным состояниям агентов.

Так же, насколько я понимаю, в коде есть важное отличие. В реализации CAF если философу не удалось взять вилку, он возвращается в состояние thinking, но тут же отсылает себе сообщение eat, т.е. сразу же начинает попытки взять вилки снова. В реализации SObjectizer если агент не смог взять вилки, то он возвращается в состояние st_thinking и какое-то время думает, не пытаясь вернуться к столу. Впрочем, это как раз интересный момент для читателей: насколько просто из каждого примера понять точный алгоритм работы :)

Еще одно замечание перед слайдами. Код CAF написан в своем стиле, с использованием директив using namespace, поэтому в нем используются короткие имена, например, actor и behavior вместо caf::actor и caf::behavior. Тогда как код из SObjectizer написан в своем стиле и using namespace в нем не используются, поэтому там so_5::rt::agent_t вместо agent_t. Я не стал уменьшать объем кода в SObjectizer потому, что это гораздо ближе к реальному использованию SO в больших проектах.

Начнем с самого простого. С кода агентов, которые выступают в качестве вилок. Слева код из CAF, справа из SObjectizer:

void chopstick(event_based_actor* self) {
  self->become(
    on(atom("take"), arg_match) >> [=](const actor& philos) {
      // tell philosopher it took this chopstick
      self->send(philos, atom("taken"), self);
      // await 'put' message and reject other 'take' messages
      self->become(
        // allows us to return to the previous behavior
        keep_behavior,
        on(atom("take"), arg_match) >> [=](const actor& other) {
          self->send(other, atom("busy"), self);
        },
        on(atom("put"), philos) >> [=] {
          // return to previous behaivor, i.e., await next 'take'
          self->unbecome();
        }
      );
    }
  );
}
class a_fork_t : public so_5::rt::agent_t
{
public :
   a_fork_t( so_5::rt::environment_t & env )
      :  so_5::rt::agent_t( env )
   {}

   virtual void
   so_define_agent() override
   {
      this >>= st_free;

      st_free.handle( [=]( const msg_take & evt )
            {
               this >>= st_taken;
               so_5::send< msg_taken >( evt.m_who, so_direct_mbox() );
            } );

      st_taken.handle( []( const msg_take & evt )
            {
               so_5::send< msg_busy >( evt.m_who );
            } )
         .handle< msg_put >( [=]() { this >>= st_free; } );
   }

private :
   const so_5::rt::state_t st_free = so_make_state( "free" );
   const so_5::rt::state_t st_taken = so_make_state( "taken" );
};

среда, 8 октября 2014 г.

[prog.flame] HelloWorld Battle: Theron-6.0 vs SObjectizer-5.5.1 :)

Да вот просто так, под влиянием ожесточенных обсуждений :)

Слева Theron, справа SObjectizer. Все комментарии удалены дабы не отвлекать внимание. Если кому-то нужны пояснения, то вот сюда (по сравнению с LOR-овским вариантом из SO-примера удалена секция try-catch, т.к. в Theron-варианте ее тоже нет).

#include <Theron/Theron.h> 

class Printer : public Theron::Actor
{
public:
  Printer(Theron::Framework &framework) : Theron::Actor(framework)
  {
    RegisterHandler(this, &Printer::Print);
  }

private:
  void Print(const std::string &message, const Theron::Address from)
  {
    printf("%s\n", message.c_str());

    Send(0, from);
  }
}; 

int main()
{
  Theron::Framework framework;
  Printer printer(framework);

  Theron::Receiver receiver;

  if (!framework.Send(
      std::string("Hello world!"),
      receiver.GetAddress(),
      printer.GetAddress()))
  {
    printf("ERROR: Failed to send message\n");
  }

  receiver.Wait();
}
#include <iostream>

#include <so_5/all.hpp>

struct message : public so_5::rt::message_t
{
  std::string msg;

  message( std::string m ) : msg( std::move(m) ) {}
};

class printer : public so_5::rt::agent_t
{
public :
  printer( so_5::rt::environment_t & env ) : so_5::rt::agent_t( env )
  {
    so_subscribe_self().event( &printer::print );
  }

private :
  void print( const message & evt )
  {
    std::cout << evt.msg << std::endl;
  }
};

int main()
{
  so_5::launch( []( so_5::rt::environment_t & env ) {
    auto agent = new printer( env );
    env.register_agent_as_coop( "hello", agent );

    agent->so_direct_mbox()->run_one().wait_forever()
      .make_sync_get< message >( "Hello, World" );

    env.stop();
  } );
}

[prog.c++] Пример подводного камня в разработке SObjectizer

Когда-то я спрашивал, будет ли кому-нибудь интересно читать про подводные камни. Вот как раз свежий пример, с пылу-с-жару :)

Чтобы объяснить суть проблемы, зайду издалека. Насколько я помню, часть решений по SO5 принималась в начале 2010 года, когда в нашем распоряжении были компиляторы с ограниченной поддержкой C++11, сам C++11 еще не был официально принят и наши знания C++11 так же были поверхностными, т.к. основную часть работы мы делали на C++03. Поэтому в свое время мы не могли использовать приемы, которые сейчас кажутся идеоматическими. Например, отсылка сообщения выполнялась посредством создания динамического объекта и передачи его в метод deliver_message:

// Простым способом.
m_left_fork->deliver_message( new msg_take( reply_mbox ) );
// Или более многословным, но и более надежным.
std::unique_ptr< msg_take > msg( new msg_take( reply_mbox ) );
m_left_fork->deliver_message( std::move( msg ) );

Собственно, тут в тему было бы наличие в C++11 функции make_unique, аналогичной make_shared, чтобы можно было писать:

// Безопасный подход, и не слишком многословный.
m_left_fork->deliver_message( std::make_unique< msg_take >( reply_mbox ) );

Поскольку не понятно, сколько мои коллеги будут продолжать использовать SO5 под C++11 без возможности перейти под C++14, в SO-5.5.1 вчера были добавлены функции so_5::send, которые сочетают в себе deliver_message и make_unique:

// Безопасный и лаконичный подход из SO-5.5.1.
so_5::send< msg_take >( m_left_fork, reply_mbox );

Еще одним хорошим качеством so_5::send является то, что отсылка сигнала (т.е. сообщения без данных внутри) выполняется точно таким же образом, что и отсылка сообщения:

// До SO-5.5.1 нужно было писать так:
m_left_fork->deliver_signal< msg_put >();

// В SO-5.5.1 можно писать так:
so_5::send< msg_put >( m_left_fork );

Т.е. синтаксически отсылка сигнала стала выглядеть так же, как и отсылка сообщения. Что, имхо, должно упростить написание обобщенного кода.

Но получив функции so_5::send() для немедленной отсылки сообщений, захотелось получить so_5::send_delayed() для отложенных сообщений. Чтобы вместо:

so_environment().single_timer< msg_stop_eating >( mbox, random_pause() );

Можно было писать вот так:

so_5::send_delayer< msg_stop_eating >( mbox, random_pause() );

Проблема здесь в том, что для взведения таймера нужно иметь доступ к экземпляру so_5::rt::environment_t, который, собственно, и управляет всеми внутренностями. Поэтому аргумента типа mbox для so_5::send_delayed() недостаточно -- в mbox-е нет ссылки на environment_t, к которому mbox принадлежит.

Посему сейчас возникает дилемма: либо хранить ссылку на environment_t внутри mbox-а, либо же передавать в so_5::send_delayed еще один аргумент.

Первый вариант приведет к тому, что в каждом mbox-е будет дополнительно 4/8 байт на ссылку, которая большинству mbox-ов может быть и не нужна. А с учетом того, что в приложении могут быть сотни тысяч/миллионы агентов (а значит и mbox-ов), набегает не так уж и мало. Напомню, что расход памяти плох не столько из-за недостаточности ее объемов (хотя это от задачи зависит). А из-за того, что чем больше размеры объектов, тем больше промахов мимо кэша при обращении к разным объектам, а промахи мимо кэша при нынешних скоростях процессоров -- это очень дорого. Т.е. память все еще ресурс, чтобы себе не думали Java-разработчики ;)

Второй вариант приведет к том, что обращение к so_5::send_delayed будет более многословным, чем хотелось бы. Т.е.

// Хотелось бы писать так:
so_5::send_delayer< msg_stop_eating >( mbox, random_pause() );

// Но придется писать так:
so_5::send_delayer< msg_stop_eating >( this, mbox, random_pause() );

// Что не сильно далеко ушло от того, что уже есть:
so_environment().single_timer< msg_stop_eating >( mbox, random_pause() );

Вот очередной подводный камешек, на который "повезло" наткнуться. Нужно выбрать один из вариантов, а затем расплачиваться за него все оставшееся время :) Приходится выбирать. Но осторожно. Но выбирать :)))

[prog.c++] Нужен совет по украшательству кода в SObjectizer

Фактически, по следам вчерашней темы. Понравилось мне сокращать многословность :) Теперь боюсь, что не смогу вовремя остановится ;)

Вот, например. Один из краеугольных камней в SObjectizer -- это наличие явно выраженных состояний агентов. Причем не только в SObjectizer, в libcaf/libcppa состояния тоже есть, но называются они там behavior. Что не удивительно, т.к. и в SO, и в CAF, агенты представляют из себя конечные автоматы, а какие же КА без состояний? :)

Так вот, до вчерашнего дня смена состояния агента выполнялась методом so_change_state()

virtual void
so_define_agent() override
{
  so_change_state( st_free );

  so_subscribe( so_direct_mbox() ).in( st_free )
    .event( [this]( const msg_take & evt )
      {
        so_change_state( st_taken );
        evt.m_who->deliver_signal< msg_taken >();
      } );

Очень долгое время такой вариант казался нормальным, хотя иногда проскакивала мысль, что state.activate() выглядит получше. В конце-концов попробовал, вроде действительно получше:

virtual void
so_define_agent() override
{
  st_free.activate();

  st_free.handle( [this]( const msg_take & evt )
      {
        st_taken.activate();
        so_5::send< msg_taken >( evt.m_who );
      } );

Но ведь и это не предел. А что, если задействовать переопределение операторов. Например:

virtual void
so_define_agent() override
{
  this >> st_free;

  st_free.handle( [this]( const msg_take & evt )
      {
        this >> st_taken;
        so_5::send< msg_taken >( evt.m_who );
      } );

У меня, конечно, извращенные представления о прекрасном, но мне кажется, что запись this >> st_free более явно говорит о переходе агента в состояние st_free, чем st_free.activate().

Соответственно, интересует мнение читателей: есть ли смысл делать this >> st_free вместо st_free.activate()? Или это, напротив, будет шагом к превращению программы в криптограмму?

Следующий момент связан с тем, что давно привычная цепочка подписки посредством so_subscribe().in().event() позволяла определить один обработчик события для нескольких состояний сразу (что удобно, если реакция на сообщение/сигнал в этих состояниях одинаковая):

so_subscribe( mbox )
  .in( st_waiting_connection_result )
  .in( st_waiting_auth_result )
  .in( st_waiting_reconnection )
  .in( st_closing_session )
  .event(
      so_5::signal< msg_shutdown >,
      &connection_handler::evt_shutdown );

Теперь для подписки можно использовать метод state_t::handle, но он привязывает обработчик только к одному состоянию. И данный с использованием этого метода будет выглядеть так:

st_waiting_connection_result.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );
st_waiting_auth_result.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );
st_waiting_reconnection.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );
st_closing_session.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );

Что, на мой взгляд, гораздо хуже. Посему напрашивается вариант с переопределением operator+()

(st_waiting_connection_result + st_waiting_auth_result + st_waiting_reconnection + st_closing_session)
  .handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );

Имена состояний не зря взяты такими длинными, это очень похоже на то, что используется в "боевых" приложениях.

Тут мне нужен второй совет: есть ли смысл определять подобным образом operator+() для привязывания обработчика сразу к нескольким состояниям? Или это еще один способ обсфукации кода?

Важное дополнение: старые методы и приемы никуда не уходят, они остаются. Причем не только для сохранения совместимости (ради этого они бы остались в любом случае), но и потому, что именно они сидят под капотом в новых, более компактных вариантах подписки. Поэтому пользователь сможет использовать любой удобный ему подход.

PS. Совсем уже "оторванный" вариант фантазия подсказала:

virtual void
so_define_agent() override
{
  st_normal.handle( m_notification_mbox,
      [this]( const msg_config_changed & evt ) {
        accept_new_config( evt );
      } );

  st_normal.handle< msg_shutdown >( m_notification_mbox,
      [this]() {
        initiate_shutdown();
      } );
}
virtual void
so_define_agent() override
{
  st_normal & m_notification_mbox +=
      [this]( const msg_config_changed & evt ) {
        accept_new_config( evt );
      };

  st_normal & m_notification_mbox & signal< msg_shutdown > +=
      [this]() { initiate_shutdown(); };
}

вторник, 7 октября 2014 г.

[prog.c++] Попробовал уменьшить многословность SO-5.5

Полезно иногда поспорить до хрипоты, защищая свою разработку :) Поневоле начинаешь смотреть на нее чужими глазами. И те вещи, которые раньше тебя устраивали вдруг оказываются... не очень удачными :)

Вот и сегодня посидел над примером и понял, что код подписки агентов, который был, вроде как, вполне себе нормальным, стал раздражать многословностью. Покурил, вроде бы чутка уменьшил количество буковок. Слева старый код, справа новый:

virtual void
so_define_agent() override
{
  so_change_state( st_free );

  so_subscribe( so_direct_mbox() ).in( st_free )
    .event( [this]( const msg_take & evt )
      {
        so_change_state( st_taken );
        evt.m_who->deliver_signal< msg_taken >();
      } );

  so_subscribe( so_direct_mbox() ).in( st_taken )
    .event( []( const msg_take & evt )
      {
        evt.m_who->deliver_signal< msg_busy >();
      } )
    .event( so_5::signal< msg_put >,
      [this]()
      {
        so_change_state( st_free );
      } );
}
virtual void
so_define_agent() override
{
  st_free.activate();

  st_free.handle( [this]( const msg_take & evt )
      {
        st_taken.activate();
        so_5::send< msg_taken >( evt.m_who );
      } );

  st_taken.handle( []( const msg_take & evt )
      {
        so_5::send< msg_busy >( evt.m_who );
      } )
    .handle< msg_put >(
      [this]() { st_free.activate(); } );
}

воскресенье, 5 октября 2014 г.

[prog.c++] Отличная презентация "Exception Safe Coding" с CppCon2014

Лежит здесь. Слайдов много. Но действующим C++ разработчикам, особенно не освоившим еще вполне C++11 -- смотреть обязательно!

Очень показателен пример из презентации: как переписали функцию, не использовавшую исключения, и что из этого получилось.