вторник, 1 января 2030 г.

О блоге

Более двадцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в последние пару лет как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на LinkedIn). Поэтому в моем блоге много заметок о работе, в частности о программировании и компьютерах, а так же об управлении.

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

среда, 23 июля 2014 г.

[prog.c++] Небольшая ошибка у Страуструпа в C++11 FAQ

Продолжая штудировать C++11 FAQ Бьёрна Страуструпа обнаружил маленькую ошибку в двух примерах кода:

Это в разделе "Algorithms improvements".

Смысл проблемы в том, что нет смысла в попытке сортировать контейнер unique_ptr-ов посредством функтора, получающего свои аргументы по значению. В этом в принципе нет смысла, т.к. при копировании unique_ptr по значению должен происходить перенос владения из старого unique_ptr в новый, т.е. владение должно было бы быть передано аргументам функтора после чего объекты были бы разрушены при выходе из функтора.

Именно потому, что в этом в принципе нет смысла, в std::unique_ptr конструктор и оператор копирования, получающие в качестве аргумента константную ссылку на std::unique_ptr, помечены как delete, т.е. запрещены к использованию. Именно поэтому, если у нас есть:

void f(std::unique_ptr<Big> a) {...}

Мы не можем написать так:

std::unique_ptr<Big> b(...);
f(b);

А должны писать одним из следующих способов:

f(std::move(b)); // Явное преобразование к rvalue reference.
                 // После этой конструкции b уже ничего не контролирует,
                 // владение объектом Big передано аргументу функции f().

f(std::unique_ptr<Big>(new Big(...))); // Неявный rvalue reference.
                 // Для конструирования аргумента a функции f()
                 // вызывается move constructor для std::unique_ptr.

Поэтому код из примеров Страуструпа не скомпилируется. Для того, чтобы функторы работали так, как задумывалось, из прототипы должны были бы быть объявлены вот так:

template<class P> struct Cmp { // compare *P values
  bool operator() (const P & a, const P & b) const { return *a<*b; }
 }

и

sort(vb.begin(),vb.end(),
  [](const unique_ptr<Big> & a, const unique_ptr<Big> & b) { return *a<*b; });

Под катом автономный пример, с которым можно поиграться самостоятельно.

понедельник, 21 июля 2014 г.

[prog.c++] Предварительная информация о новом thread_pool-диспетчере в SObjectizer

В рамках разработки SObjectizer 5.4.0 как-то буднично произошло совсем не рядовое событие: полку штатных диспетчеров, доступных пользователю "искаропки" прибыло :) Появился thread_pool-диспетчер, который запускает привязанных к нему агентов на пуле рабочих потоков. До сих пор пользователям было доступно всего три диспетчера, появившихся в SO-4 еще в 2002-2003-м годах, а именно:

  • самый простой диспетчер с одной рабочей нитью (one_thread-диспетчер). Все привязанные к нему агенты работают на одной общей рабочей нити. Если какой-то из агентов "подвиснет", то подвиснут и все остальные агенты на этой рабочей нити;
  • диспетчер с активными объектами (active_obj-диспетчер). Каждому привязанном к диспетчеру агенту выделяется своя собственная рабочая нить. Если агент зависает, то зависает только его нить, на других агентов это не влияет;
  • диспетчер с активными группами (active_group-диспетчер). Рабочая нить выделяется группе агентов. Соответственно, если один агент из группы подвиснет, то подвиснут и остальные члены этой группы. Но другие агенты, принадлежащие другим группам (как и принадлежащие другим диспетчерам), смогут продолжать свою работу на своих рабочих контекстах.

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

Релиз SO-5.4.0, по оптимистичным оценкам, состоится не раньше второй половины августа, поэтому "в мир" новый thread_pool-диспетчер выйдет еще не скоро. Но я попробую рассказать о нем подробнее уже сейчас, дабы получить возможность обсудить сделанное и, при необходимости и появлении более удачных идей/предложений, иметь время на переработку. Для заинтересовавшихся рассказ о thread_pool-диспетчере под катом.

воскресенье, 20 июля 2014 г.

[prog.thoughts] Чтобы писать надежные многопоточные приложения...

...с использованием таких низкоуровневых механизмов как нити, мутексы/семафоры/мониторы, не говоря уже про еще более низкоуровневые средства вроде атомиков и барьеров, нужно:

  • либо быть очень умным человеком, чтобы уметь перебирать в голове все те невероятные комбинации событий, которые могут случится при активном взаимодействии нескольких потоков, и писать код, изначально учитывающий все это многообразие;
  • либо же иметь особую карму (суть "эффект Паули", суть "генеральский эффект", суть "везение на баги"), чтобы подавляющее количество багов вылазило на поверхность при небольшом количестве запусков небольшого количества тестов, имитирующих небольшое количество возможных ситуаций.

Ну и плюс к тому, нужно быть чрезмерно самоуверенным и крайне оптимистичным разработчиком, чтобы ввязываться в такие авантюры :)))

пятница, 18 июля 2014 г.

[prog] Кому-нибудь будет интересно читать про подводные камни в разработке SObjectizer?

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

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

Лично я вижу смысл таких заметок в том, что останутся следы, объясняющие, почему SObjectizer устроен и работает именно так, а не иначе. Но вот будет ли это интересно еще кому-то кроме меня -- не знаю. Поэтому прошу оставить свое мнение в комментариях. Если желающих читать такие опусы наберется 15-20 человек, тогда попробую начать что-то вроде "путевых заметок". Если не наберется, не буду никого грузить своими личными проблемами :)

Upd. Проголосовало "За": 7

среда, 16 июля 2014 г.

[prog.c++] std::this_thread::get_id() -- очень дорогая штука под Windows и MSVS2013

Просто на удивление. Так что, если кому-то нужно очень часто вызывать std::this_thread::get_id(), то это может привести к серьезной просадке производительности.

Под катом исходник standalone benchmark-а, на котором я проводил замеры. Под Windows 8.1 и MSVS2013 Express у меня получается чуть меньше 2M вызовов get_id в секунду (компиляция через cl -EHsc -O2). Тогда как под тем же Windows 8.1 и Cygwin (GCC 4.8.3) получается уже чуть больше 10M в секунду (компиляция через g++ -std=c++11 -O3). А под VirtualBox-ом, ArchLinux и GCC 4.9.0 -- уже больше 400M! (компиляция такая же: g++ -std=c++11 -O3).

Причем это явно какая-то хитрая особенность реализации std::thread::id и std::this_thread::get_id() в MSVS. Т.к. если тупо пользоваться GetCurrentThreadId(), то этот же тест выдает более 500M в секунду при тех же самых параметрах компиляции.

вторник, 15 июля 2014 г.

[prog.c++] Еще про тонкости ACE (не забывать про #include в .cpp-файлах с main-ом)

Сегодня из-за собственной забывчивости убил несколько часов на разбирательство с крахом совершенно безобидного unit-теста. Сначала удалось выяснить, что падение происходит при попытке обращения к ACE-овскому Timer_Queue_Adapter-у. Потом оказалось, что к такому поведению привело изъятие #include <ace/Basic_Types.h> из одного из заголовочных файлов.

Ларчик открывался просто. В каждом cpp-файле, где определяется main(), необходимо делать #include <ace/OS.h> (или OS_main.h, или ACE.h -- главное, чтобы хотя бы один из ACE-овских файлов был заргужен). Это необходимо потому, что ACE делает свое определение функции main, в котором скрывается инициализация и деинициализация внутренностей ACE. А символ main после этих определений оказывается всего лишь #define-ом, при использовании которого пользователь определяет не реальный main, а всего лишь вспомогательную для ACE функцию ace_main_i().

Вот в моем unit-тесте не было #include <ace/OS.h>, но ранее все работало из-за того, что в других заголовочных файлах подгружались ACE-овские заголовки и main() для ACE определялась правильно. Когда же я проделал рефакторинг, выяснилось, что больше никакие ACE-овские заголовки в unit-тесте не загружаются, main оказывается настоящей main, внутренности ACE не инициализируются должным образом, отсюда и крах приложения.

В общем, актуальность отказа от ACE в SObjectizer в очередной раз появилась на повестке дня. Правда, сделать это будет не так уж и просто. На вскидку, без тщательного изучения кодовой базы, можно говорить про следующие вещи, в которых ACE нужно будет заменить на что-то еще:
  • логирование сообщений о фатальных ошибках в ядре so_5. Это не критичная функциональность. Можно либо тупо перейти на работу только с std::cerr, либо же определить интерфейс, реализацию которого можно будет назначить через so_environment_params_t (по умолчанию будет задействована реализация на основе std::cerr);
  • реализация timer_thread. Очень важная штука, без таймеров нет SObjectizer-а. Сейчас в so_5 интерфейс timer_thread реализуется через ACE_Thread_Timer_Queue_Adapter. Можно сделать свою реализацию на основе только стандартной библиотеки C++11 (посредством std::map для хранения заявок и std::condition_variable::wait_until для ожидания наступления очередной заявки или поступления запроса на создание новой заявки/удаления существующей). Реализация не кажется слишком уж сложной, но пока она не будет в должной мере протестирована в реальных проектах коэффициент моего спокойного сна будет совсем никакой ;)
  • в подпроекте so_log нужно будет много чего переписать, т.к. там сейчас определены собственные реализации Backend-ов для ACE_Logging. Вроде бы ничего сложного;
  • самым кардинальным образом нужно будет переделать so_5_transport, в котором вся работа с сокетами построена на ACE_Socket-ах и ACE_Reactor-ах. Очень нехилый кусок работы. Тут важно выбрать адекватную замену и понять, какие же выигрыши будут от такой замены. В качестве альтернатив ACE я бы здесь рассматривал libevent/libev, libuv и, может быть, Boost.Asio. На крайний случай можно и POCO. Если кто-то знает другие хорошие библиотеки для организации сетевого взаимодействия (с нормальной поддержкой Windows и Linux), прошу поделиться знаниями;
  • работа с DLL в so_sysconf. В принципе, это довольно простая для создания кросс-платформенности абстракция, так что можно будет сделать свою обертку над LoadLibrary и dlopen;
  • запуск приложения в режиме сервиса Windows или демона Unix, так же используется в so_sysconf. Такие вещи я сам вручную не делал, использовал функциональность ACE. На что заменять и насколько сложно сделать свою реализацию вручную не имею представления;
  • во многих примерах/тестах/приложениях используется функциональность ACE_Get_Opt для работы с аргументами командной строки. И хотя кода для работы с ACE_Get_Opt приходится писать много, результат того стоит, т.к. обеспечивается чуть ли не полная поддержка POSIX-овского стандарта на формат аргументов командной строки. На что менять не знаю. На ум сразу приходит Boost.ProgramOptions, но стоит ли только ради этого закладываться на зависимость от Boost-а -- не понятно.
В общем, работы нужно будет проделать прилично. Оправдано ли это -- не знаю, далеко не уверен. Если бы к нам были обращения о том, что SObjectizer -- штука хорошая, но использованию препятствует ACE, можно было бы проделать полный переход с ACE на что-то другое. Но таких обращений не было, поэтому приоритет у этой задачи самый маленький.

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