Итак, текущая реализация синхронного взаимодействия SObjectizer-агентов в новой, разрабатываемой сейчас версии 5.3 приобрела стабильные очертания. Что дает возможность рассказать о том, что это такое, откуда растут ноги, и показать, как этим можно пользоваться. Поскольку тема специфическая и содержит большое количество текста, то она упрятана под кат, дабы не мешать неинтересующимся.
четверг, 12 июня 2014 г.
[prog] Суворов Александр Васильевич о маленьких командах :)
Где меньше войска, там больше храбрых.
[prog] Кстати, о трудоемкости программирования. На личном примере.
Сложно сказать, насколько эти случаи распространены. К сожалению, я и сам попался на эту удочку. Хотя честно пытался бороться с этим эффектом и старался себя сдерживать, но не всегда это удавалось :(
Тем интереснее, после руководящей работы, вновь почувствовать себя в шкуре программиста и ощутить на себе трудоемкость этого трудно прогнозируемого занятия. На личном, так сказать, примере.
В течении последней недели я занимался реализацией синхронного взаимодействия агентов в SObjectizer. На рождение идеи ушло где-то два дня. И еще дня полтора на получение первой реализации. Казалось, что осталось всего-ничего: пару тестов, один-два примера и всех делов.
Однако сейчас, когда все это уже закончено, можно оглянуться назад и сказать, что в действительности эти "пара тестов и один-два примера" заняли намного больше, чем я ожидал. Как по времени, так и по усилиям.
Проиллюстрировать это можно в цифрах. Хотя LOC-и (т.е. количество строк кода) -- это параметр не очень адекватный, но в данном случае подойдет и он.
Итак, создание первой рабочей версии привело к увеличению количества строк в проекте на ~500 штук (с 16271 на ревизии 570 до 16744 на ревизии 573). Тут считаются только новые строки, еще какое-то их количество было переработано, а что-то и вовсе выброшено. Ну да не суть. Важно то, что в эти 500 строк уложились основные изменения, которые затем правились еще, но уже не кардинальным образом. Т.е., грубо говоря, ядро изменений, на придумывание которого ушло два дня, и еще день на реализацию -- это эти самые 500 строк кода.
А вот когда новый код был покрыт тестами и проиллюстрирован несколькими примерами, размер проекта увеличился еще на ~2000 строк (18977 на ревизии 597). И опять таки, для простоты считаются только новые строки, а те, которые перерабатывались и удалялись от ревизии к ревизии, не учитываются. Т.е. фактической работы было еще больше.
Промашка по срокам на создание всей этой тестовой и демонстрационной обвязки составила два раза: 4 дня вместо предполагавшихся мной 2.
При том, что когда неделю назад я брался за идею реализации синхронных сервисов, у меня вообще не было представления о том, как эта задача будет решена, сколько времени потребуется на поиск решения, и сколько потом еще нужно будет на реализацию и тестирование. Просто повезло, что идея возникла буквально внезапно, как вспышка: бах и все, вот она, на ладошке! :)))
И еще одно крайне немаловажное уточнение: над SObjectizer-ом я сейчас работаю просто в идеальных условиях: трачу на него столько времени, сколько нужно. Мои усилия ограничиваются только моими физическими возможностями. Это прекрасно известная мне предметная область и хорошо знакомый мне код. Я пользуюсь инструментами, которыми владею достаточно уверенно и про которые легко найти необходимую информацию в Интернете. Надо мной не давлеют сроки. Мне не нужно общаться с заказчиком и/или руководством. Меня не отвлекают работающие по соседству программисты или тестировщики, или внедренцы, или суппорт. Мне не нужно тратить время на дорогу в офис и обратно...
В общем, абсолютно точно могу сказать, что ни у одного ведущего разработчика, выполняющего важный кусок работы в офисе, практически не будет таких тепличных условий для полной концентрации над своей задачей. Посему если я сейчас промахнулся с оценкой сроков тестирования в два раза, то работая в офисе эта ошибка легко составила бы 3-4, а то и более раз.
Подчеркну, что здесь даже и речи не идет о том, что рабочее время может тратиться на какие-то непроизводственные вещи, вроде хождения на офисную кухню за чаем или случайные разговоры за жизнь. Просто в офисе к твоим производственным обязанностям относится еще столько мелочей, которые незаметно отнимают массу времени, что просто невероятно...
К сожалению, чем дальше ты уходишь от разработки в начальствование, тем меньше ты об этом помнишь. Отсюда и неправильное мнение о том, с какой скорость должна идти работа. Хотя, казалось бы, всего год-полтора назад ты об этом прекрасно помнил! :)
Ну а под занавес, дабы два раза не вставать, добавлю еще несколько слов про ценность программиста. ЕМНИП, я неоднократно писал у себя в блоге, а еще чаще устно говорил руководителям разных рангов, что одна из тяжелейших потерь, связанных с уходом разработчика -- это потеря тех идей, которые были у него в голове. Большинство этих идей никогда и нигде формально не фиксируется! Но, даже если фиксируется, то не разжевывается до таких подробностей, чтобы любой другой программист взял и воплотил их в коде. В лучшем случае остаются следы этих идей от кулуарных разговоров и споров в памяти их участников (если они сами продолжают работать в компании).
В качестве демонстрации приведу пример из той же реализации синхронных сервисов в SObjectizer. Дабы не забывать что еще нужно сделать, я завел в Wiki простой TODO-лист, который начался всего с нескольких строк, имеющих отношение к синхронным сервисам (вот этот вариант). Но затем он трансформировался во все более и более подробный перечень разных вкусностей и полезностей (в том числе и крайне спорных). Вот, к примеру, один из промежуточных вариантов. И это при том, что я далеко не сразу фиксирую там то, что приходит в голову. Какие-то вещи требуют "вызревания" для формализации. Какие-то требуют борьбы с собственной ленью :) А ведь далеко не все разработчики имеют привычку вот такой фиксации своих идей. Особенно, если речь идет не о собственном проекте, а о том, чем ты занимаешься на работе "для дяди"...
Так что, дорогие начальнички, если вы хотите успешного выполнения проектов, то берегите своих разработчиков (а так же тестировщиков, технических писателей, внедренцев, саппортеров, дизайнеров и т.д.). Без вас проекты будут выполняться, уж можете мне поверить. А вот без них...
среда, 11 июня 2014 г.
[prog.c++] Анонс: Очередная встреча C++ User Group в Санкт-Петербурге 21 июня
21 июня в Санкт-Петербурге пройдет очередная встреча C++ User Group. Это мероприятие — возможность общения для разработчиков, пишущих на C++, где можно поговорить именно о C++ и связанных вопросах. На встрече выступят докладчики из Москвы, Санкт-Петербурга.
ПрограммаУчастие бесплатное, но требуется предварительная регистрация (по приведенной выше ссылке).
11.30 Регистрация
12.00 Максим Хижинский, “Flat Combining — легкий путь в сложный мир конкурентных контейнеров”
13.00 Григорий Демченко, “Fine-grained locks”
14.00 Перерыв
14.30 Антон Бикинеев, “Generic Numeric Programming with Boost.Math and Boost.Multiprecision”
15.30 Дмитрий Нестерук, “Метод Монте-Карло на C++“
Место проведения
Гостиница «Октябрьская», зал «Синий» (Санкт-Петербург, Лиговский проспект, дом 10).
PS. Репост приветствуется.
вторник, 10 июня 2014 г.
[prog.c++] Да уж, засада с этим ACE 6.2.6 (а так же MSVC, MinGW-w64 и пр.)...
Текущая версия ACE (6.2.6 на данный момент) поддерживает только MSVC 11.0, и не имеет готовых sln-файлов для MSVC 12.0.
При этом в MSVC 11.0 нет поддержки variadic templates и есть баги в реализации std::future (например, мне очень мешает вот этот). Вроде как MSVC 12.0 все это уже умеет, но ACE пока его не поддерживает официально... :(
MinGW-w64 предоставляет два варианта GCC: один win32, второй posix. В первом нет поддержки для C++11 threading (т.е. нет нитей, promise, future и т.д.). А во втором, который posix, все это есть (хоть и не быстрое). Но ACE не может скомпилироваться в posix-варианте GCC от MinGW-w64 из-за того, что в этом варианте есть куча макросов вроде localtime_r. Для ACE это проблема, т.к. ACE пытается определять localtime_r самостоятельно, да еще в собственном пространстве имен ACE_OS, чему подобные макросы сильно препятствуют... Про эту проблему знают еще с версии 6.2.1, даже предлагали патчи и оформляли тикет в bugzilla, да только воз и ныне там... :(
Пичалька. Пока спасает наличие нормального GCC 4.8.3 под Cygwin-ом. Ну и остается надеяться на то, что ACE-овцы таки сделают поддержку MSVC 12.0 "искаропки"... На поддержку MinGW-w64-posix я и не надеюсь.
[prog.c++] Ну вот нравятся мне возможности современного C++!
Программирование на современном C++ сейчас -- это совсем другие ощущения, чем программирование на современном C++ десятилетней давности. И хотя тогда какой-нибудь Visual C++ 2003 с более-менее нормальной поддержкой STL был большим прорывом по сравнению с Visual C++ 6.0, но все равно, количество необходимых приседаний было слишком большим. Да и общая атмосфера была такая, что казалось, что C++ будет неотвратимо превращаться в устаревший, мало кем и где используемый маргинальный язык.
Тем не менее, время шло, долгострой под названием C++0x успешно завершился и его результаты (например, в Visual C++ 2012/2013 или GCC 4.8/4.9) не могут не радовать. Например, пару дней назад перерабатывал старый класс, в нескольких методах которого нужно было простым поиском найти элемент в векторе простых структур. Поскольку операция эта 1-в-1 повторялась во всех методах, то возникла мысль вынести обращение к std::find_if в один вспомогательный метод, а дальше дергать только его. Но...
Но есть нюанс :) В C++ не получится ограничится вот такой простой реализацией:
typedef std::vector< state_and_handler_t > handler_container_t; handler_container_t::iterator try_find_handler( handler_container_t & where, const state_t & search_key ) { return std::find_if( where.begin(), where.end(), ... ); } |
Поскольку, если экземпляр handler_container_t является атрибутом какого-то объекта, то иногда ссылка на него будет константной:
class handler_caller_t { handler_container_t m_handlers; ... public : void call_handler( const state_t & current_state ) const { // Это не скомпилируется!!! handler_container_t::iterator it = try_find_handler( m_handlers, current_state ); ... } void remove_handler( const state_t & target_state ) { m_handlers.erase( try_find_handler( m_handlers, target_state ) ); } ... }; |
Проблема здесь в том, что в handler_caller_t::call() ссылка на m_handlers константна, т.к. сам метод call() константен. Поэтому ее нельзя передать в try_find_handler.
При работе в C++03 на ум сразу приходит два решения: либо тупо продублировать try_find_handler для константного и неконстантного аргументов, либо же попробовать сделать try_find_handler шаблоном, тип аргумента которого выводится. Второй вариант кажется более привлекательным, т.к. он позволяет избежать дублирования кода, но он не сработает:
template< class C > typename C::iterator try_find_handler( C & where, const state_t & search_key ) { return std::find_if( where.begin(), where.end(), ... ); } |
Проблема в возвращаемом значении. Его тип будет iterator только для неконстантного контейнера. Если же в try_find_handler передается константная ссылка, то методы begin()/end() будут возвращать const_iterator, а не iterator. Следовательно, const_iterator будет возвращаться из std::find_if. А так как const_iterator -- это отличный от типа iterator тип, то значение данного типа нельзя вернуть из такого варианта try_find_handler. Что приведет к невозможности скомпилировать шаблонный вариант try_find_handler для константного контейнера.
Честно скажу, что сходу я не смог придумать обходной маневр для решения этой проблемы в рамках C++03, хотя если задействовать шаблонную магии, он, может быть, и отыщется. Будь у меня необходимость работать жестко в рамках C++03, то ухищрениями с шаблонами я бы не занимался, а тупо бы продублировал бы код try_find_handler.
К счастью, сейчас я могу себе позволить ограничится только C++11. А в нем, за счет некоторых новых фич, появляется возможность написать шаблонный код try_find_handler всего один раз. Вот как это в итоге стало выглядеть у меня:
template< class C > auto /* (1) */ try_find_handler( C & container, const state_t & state ) -> decltype( std::begin(container) ) /* (2) */ { typedef decltype(*std::begin(container)) value_t; /* (3) */ return std::find_if( std::begin( container ), std::end( container ), [&state]( value_t & o ) { return o.m_state == &state; } ); } |
Ключевые моменты помеченны в комментариях циферьками.
Пункт первый: в С++11 можно не писать сразу тип возвращаемого функцией/методом значения, а вместо этого поставить ключевое слово auto. Это означает, что тип возвращаемого значения будет уточнен после декларации аргументов функции. Что позволяет задействовать типы аргументов в процессе вывода типа возвращаемого значения.
Именно это показано в пункте (2). Там за счет еще одной новой фишки C++11 -- decltype -- определяется, какой тип возвращает std::begin() для данного типа контейнера. Не суть важно, будет ли это const_iterator, iterator или что-то еще. Важно, что компилятор определит это сам, и будет считать, что значение именно этого типа будет возвращаться самой функцией try_find_handler().
Пункт три, хоть и показывает еще раз возможности C++11, на самом деле нужен для того, чтобы обойти ограничение, которое еще есть в C++11, но которого не будет в C++14. Дело в том, что в std::find_if передается лямбда-функция. А в ее декларации нужно указать, какой тип будет у ее аргумента. В C++11 это нужно сделать явно, а вот в C++14 можно будет указать вместо типа auto и тип аргумента выведет компилятор.
В данном случае, поскольку я имею дело с собственным контейнером, можно было бы тупо указать тип его элемента, т.к. я все равно его знаю. Но хотелось задействовать C++11 на полную катушку и пойти максимально далеко :) Да, такой overuse для полезной feature, но получилось интересно :)
Так вот, в пункте (3) за счет decltype определяется, какой тип будет, если разыменовать возвращенный std::begin() итератор. Т.е. тип значения, на которое ссылается итератор. Этому типу через typedef дается псевдоним value_t, а затем value_t используется в декларации лямбда-функции.
Еще один маленький, а может и не маленький, бонус мы получаем в связи с использованием std::begin/std::end вместо std::vector::begin/end. Это позволяет нам использовать в качестве контейнера обычные C-шные массивы, у которых нет методов begin/end. Но зато с ними прекрасно работают свободные функции std::begin/end.
Полный текст автономного примера, демонстрирующего try_find_handler для разных типов контейнеров, приведен под катом.
Еще раз отмечу, что C++11, на мой взгляд, настолько далеко продвинул C++ вперед, что сейчас он воспринимается, если не как совсем новый язык, то уж как очень сильно усовершенствованный язык точно. Конечно, утечки памяти, обращения по невалидным указателям и прочие прелести native-разработки никуда не делись, хотя за счет усовершенствования стандартной библиотеки количество граблей несколько уменьшилось. Но общее удовольствие от работы на C++ сейчас гораздо выше. В чем-то даже похоже на удовольствие, которое я когда-то получал от программирования на Ruby. Только при этом язык позволяет писать очень большие и намного более эффективно работающие программы :)
И еще одно впечатление. Лет десять назад мне не нравилось, что происходит вокруг C++, т.к. понимать навороченные C++ные программы становилось все сложнее и сложнее. Аналогичное очущения возникают и сейчас. Но вот причины усложнения понимания кода совершенно разные.
Раньше C++ные возможности пытались эксплуатировать для того, чтобы получить что-то, чего в языке не было. Навороченные шаблоны имени Александреску, Вандервуда или Джосаттиса тому пример (особенно мне нравится в качестве примера приводить старую Boost.Lambda). Поэтому и прикладной код получался не слишком понятным, а уж во вспомогательные библиотеки и вовсе страшно было заглядывать.
Теперь же C++ настолько продвинулся, что сложность его восприятия возникает из-за его выскоуровневости (которая, при этом, в умелых руках не противоречит эффективности). Так, рассматривая некоторые примеры кода на современном C++ возникает чувство, что читаешь какой-то Haskell-ный код, который чуть-чуть больше разбавлен анотациями и в котором используются нормальные, а не однобуквенные с апострофами, идентификаторы :) Вот, например, коротенький пример из отличной статьи Бьёрна Страуструпа "Software Development for Infrastructure":
template <typename Iter, typename Predicate> pair<Iter, Iter> gather(Iter first, Iter last, Iter p, Predicate pred) // move e for which pred(e) to the insertion point p { return make_pair( // from before insertion point: stable_partition(first, p, !bind(pred, _1)), // from after insertion point: stable_partition(p, last, bind(pred, _1)) ); } |
Статья, кстати говоря, классная. Рекомендую, даже не смотря на то, что она объемная и на английском. Интересные вопросы Страуструп там затрагивает, многие из которых касаются не только C++. И, если Страуструп прав, а мне кажется, что во многом он прав, то будущее С++ представляется уже не таким мрачным, как 10 лет назад. Особенно, если C++14 и C++17 не станут такими же долгостроями, как C++0x.
Ну а под катом полный текст автономного примера, с которым можно поиграться.
PS. Еще пара заметок на тему современного C++ и функционального стиля: #1, #2.
понедельник, 9 июня 2014 г.
[prog.c++] Ссылки на серию статей Энтони Уильямса по многопоточности в C++11
Статьи на английском, но читаются легко.
Multithreading in C++0x Part 1: Starting Threads
std::unique_lock<>
воскресенье, 8 июня 2014 г.
[prog.c++] Синхронность в SObjectizer: всего-то спустя 12 лет... :)
То, о чем мне так долго говорили большевики все вокруг, нашло таки свое воплощение в жизни :) В SObjectizer появилось синхронное взаимодействие между агентами. Реализовано оно на основе C++11 классов std::promise и std::future. Что дает возможность делать не просто синхронное взаимодействие, но и более хитрые варианты.
Пока это всего лишь черновой вариант, который был опробован всего на одном компиляторе, и над которым еще предстоит работать напильником. Но начало уже положено, что не может не радовать.
Полный пример использования синхронного взаимодействия можно увидеть здесь. А вот небольшая выжимка:
Ключевые фрагменты агента, который предоставляет синхронный сервис:
virtual void so_define_agent() { so_subscribe( m_self_mbox ) .service( &a_convert_service_t::svc_convert ); } std::string svc_convert( const so_5::rt::event_data_t< msg_convert > & evt ) { std::cout << "svc_convert called: value=" << evt->m_value << std::endl; std::ostringstream s; s << evt->m_value; return s.str(); } |
Ключевые фрагменты агента, который обращается к синхронным сервисам других агентов. Параметры для синхронного сервиса все равно нужно передавать в виде динамически созданного объекта, т.к. их доставка осуществляется через очередь сообщений обычным для SObjectizer-а способом.
virtual void so_evt_start() { auto hello = //NOTE: it could be a method of agent_t. so_5::rt::service< std::string >( m_svc_mbox ) .request< msg_hello_svc >(); auto convert = so_5::rt::service< std::string >( m_svc_mbox ) .request( new msg_convert( 42 ) ); std::cout << "hello_svc: " << hello.get() << std::endl; std::cout << "convert_svc: " << convert.get() << std::endl; std::cout << "sync_convert_svc: " << so_5::rt::service< std::string >( m_svc_mbox ) .sync_request( new msg_convert( 1020 ) ) << std::endl; // More complex case with conversion. auto svc_proxy = so_5::rt::service< std::string >( m_svc_mbox ); // These requests should be processed before next 'sync_request'... auto c1 = svc_proxy.request( new msg_convert( 1 ) ); auto c2 = svc_proxy.request( new msg_convert( 2 ) ); // Two previous request should be processed before that call. std::cout << "sync_convert_svc: " << svc_proxy.sync_request( new msg_convert( 3 ) ) << std::endl; // But their value will be accessed only now. std::cout << "convert_svc: c2=" << c2.get() << std::endl; std::cout << "convert_svc: c1=" << c1.get() << std::endl; so_environment().stop(); } |
Пока выглядит многословно. Но, думаю, если тщательно разобраться с С++11 variadic templates и std::forward, объем писанины можно будет посократить.
[prog.thoughts] О том, насколько же быстро в нашем ремесле все меняется...
С самого начала увлечения программированием я слышал о том, что в нашем ремесле все очень быстро меняется, старое отмирает, новое появляется, нужно постоянно учиться и переучиваться. На счет постоянно учиться -- это оказалось чистая правда. А вот со всем остальным дела обстоят не так просто :) Медленно, но неуклонно наступающий старческий маразм приближающаяся старость, хоть и не есть хорошая вещь, но дает возможность оглядываться назад и на собственном жизненном опыте наблюдать интересные вещи.
Например, готовя предыдущую заметку, я с удивлением обнаружил, что написанной мной когда-то реализации хамелеонов на C++ и ACE уже семь, повторю, СЕМЬ(!) лет. Срок очень не маленький. Тем не менее, написанный тогда код без каких-либо изменений был успешно скомпилирован на совсем-совсем другой версии библиотеки ACE и совсем-совсем, я бы даже сказал, совершенно совсем-совсем другим C++ компилятором.
Для нашего ремесла семь лет -- это очень немаленький срок. Вот, например, через пару недель исполнится семь лет "эре iPhone-ов" (первый iPhone был представлен публике 29 июня 2007 года, Wikipedia). Т.е. очень мощная, очень большая и активно развивающаяся ниша смартфонов и приложений для них, уже насчитывает семь лет своей истории. И это если не брать в расчет еще десяток лет до того, когда была эпоха Palm-ов и WinCE-устройств.
Но семь лет -- это какая-то не круглая цифра. Лучше взять, например, 10 лет. Внезапно оказывается, что у языка программирования Scala в этом году первый юбилей -- его публичный релиз состоялся в 2004-м, Wikipedia. Как-то внезапно выяснилось, что язык, который многим (в том числе и мне) казался 100% заменой и убийцей Java, уже отнюдь не молод :) Не удалось ему убить Java в свои лучшие годы, явно не получится и в дальнейшем :)))
Ну да то чужие увлечения, которые не так сильно затронули меня самого. Забавнее другое -- если все будет нормально, то через пару-тройку месяцев я буду писать для блога заметку под названием "ViM, Ruby и Mxx_ru - десять лет в пути". И это будет продолжением серии (#1, #2), начатой пять лет назад.
Десять, мать его..., лет! Не удивлюсь, если выясниться, что ViM-ом я пользуюсь дольше, чем многие из моих читателей программируют вообще. И что, за это время что-то настолько сильно изменилось, что ViM стал неактуальным?
Да чего там мелочится! :) Как выясняется, за 23 года существования ViM-a (1991-й год, Wikipedia) индустрия не смогла уйти так далеко вперед, чтобы ViM перестал быть востребованным. А если взять GNU Emacs, который еще постарше будет (это где-то 1985-й, почти тридцать лет назад)... Ведь для многих актуальных сейчас языков программирования ничего лучше-то и нет.
Кстати, об этих самых языках программирования. Вот в последние лет десять активно и много говорят о Haskell-е. А ведь Haskell появился в 1990-м (Wikipedia). Почти двадцать пять лет назад.
Или возьмем Erlang, тоже довольно популярный на околопрограммерских форумах язык (причем у меня складывается впечатление, что на практике он используется даже больше, чем Haskell). Его разработка в лабораториях Ericsson-а началась в начале 1980-х, а датой публичного релиза можно считать 1991-й год (www.erlang.org).
Ну да что там всякая маргинальная экзотика! :) Обратимся к мейнстримам из мейнстримов. Языку Java в будущем году будет 20 лет (публичный релиз в 1995-м). Столько же будет и JavaScript-у, который впервые явился миру в 1995-м под именем LiveScript, но в его название было запихнуто слово Java в качестве, к сожалению, удачного маркетингового хода. Питону через пару лет стукнет 25 (публичный релиз в 1991-м). Даже Perl, которому, по-хорошему, не нужно было давать рождаться, и которого, к большому сожалению, не может убить даже долгострой под названием Perl 6, используется уже больше 26 лет :))) /любителей Perl-а прошу обратить внимание на количество смайликов/
Ну а старому, доброму C++, который кормил меня на протяжении практически всей моей профессиональной карьеры, через два года стукнет тридцать(!). И это при том, что последние версии "древнего" C++ выглядят посовременнее Java :)
В общем, в нашем ремесле действительно мода меняется очень быстро. И вне зависимости от моды нужно постоянно учится. Но многие вещи, которые мы используем в повседневной работе, насчитывают уже не один десяток лет. И, что немаловажно, события развиваются так, что мы будем пользоваться ими еще не один десяток :) Правда, лет через 10-15-20 подобные заметки в своих блогах будут писать уже более совершенные существа :)