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

[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() );

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

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